veloren_server/
chunk_generator.rs

1use crate::metrics::ChunkGenMetrics;
2#[cfg(feature = "worldgen")]
3use crate::rtsim::RtSim;
4#[cfg(not(feature = "worldgen"))]
5use crate::test_world::{IndexOwned, World};
6use common::{
7    calendar::Calendar, generation::ChunkSupplement, resources::TimeOfDay, slowjob::SlowJobPool,
8    terrain::TerrainChunk,
9};
10use hashbrown::{HashMap, hash_map::Entry};
11use rayon::iter::ParallelIterator;
12use specs::Entity as EcsEntity;
13use std::sync::{
14    Arc,
15    atomic::{AtomicBool, Ordering},
16};
17use vek::*;
18#[cfg(feature = "worldgen")]
19use world::{IndexOwned, World};
20
21type ChunkGenResult = (
22    Vec2<i32>,
23    Result<(TerrainChunk, ChunkSupplement), Option<EcsEntity>>,
24);
25
26pub struct ChunkGenerator {
27    chunk_tx: crossbeam_channel::Sender<ChunkGenResult>,
28    chunk_rx: crossbeam_channel::Receiver<ChunkGenResult>,
29    pending_chunks: HashMap<Vec2<i32>, Arc<AtomicBool>>,
30    metrics: Arc<ChunkGenMetrics>,
31}
32impl ChunkGenerator {
33    pub fn new(metrics: ChunkGenMetrics) -> Self {
34        let (chunk_tx, chunk_rx) = crossbeam_channel::unbounded();
35        Self {
36            chunk_tx,
37            chunk_rx,
38            pending_chunks: HashMap::new(),
39            metrics: Arc::new(metrics),
40        }
41    }
42
43    pub fn generate_chunk(
44        &mut self,
45        entity: Option<EcsEntity>,
46        key: Vec2<i32>,
47        slowjob_pool: &SlowJobPool,
48        world: Arc<World>,
49        #[cfg(feature = "worldgen")] rtsim: &RtSim,
50        #[cfg(not(feature = "worldgen"))] _rtsim: &(),
51        index: IndexOwned,
52        time: (TimeOfDay, Calendar),
53    ) {
54        let v = if let Entry::Vacant(v) = self.pending_chunks.entry(key) {
55            v
56        } else {
57            return;
58        };
59        let cancel = Arc::new(AtomicBool::new(false));
60        v.insert(Arc::clone(&cancel));
61        let chunk_tx = self.chunk_tx.clone();
62        self.metrics.chunks_requested.inc();
63
64        // Get state for this chunk from rtsim
65        #[cfg(feature = "worldgen")]
66        let rtsim_resources = Some(rtsim.get_chunk_resources(key));
67        #[cfg(not(feature = "worldgen"))]
68        let rtsim_resources = None;
69
70        slowjob_pool.spawn("CHUNK_GENERATOR", move || {
71            let index = index.as_index_ref();
72            let payload = world
73                .generate_chunk(index, key, rtsim_resources, || cancel.load(Ordering::Relaxed), Some(time))
74                // FIXME: Since only the first entity who cancels a chunk is notified, we end up
75                // delaying chunk re-requests for up to 3 seconds for other clients, which isn't
76                // great.  We *could* store all the other requesting clients here, but it could
77                // bloat memory a lot.  Currently, this isn't much of an issue because we rarely
78                // have large numbers of pending chunks, so most of them are likely to be nearby an
79                // actual player most of the time, but that will eventually change.  In the future,
80                // some solution that always pushes chunk updates to players (rather than waiting
81                // for explicit requests) should adequately solve this kind of issue.
82                .map_err(|_| entity);
83            let _ = chunk_tx.send((key, payload));
84        });
85    }
86
87    pub fn recv_new_chunk(&mut self) -> Option<ChunkGenResult> {
88        // Make sure chunk wasn't cancelled and if it was check to see if there are more
89        // chunks to receive
90        while let Ok((key, res)) = self.chunk_rx.try_recv() {
91            if self.pending_chunks.remove(&key).is_some() {
92                self.metrics.chunks_served.inc();
93                // TODO: do anything else if res is an Err?
94                return Some((key, res));
95            }
96        }
97
98        None
99    }
100
101    pub fn pending_chunks(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
102        self.pending_chunks.keys().copied()
103    }
104
105    pub fn par_pending_chunks(&self) -> impl rayon::iter::ParallelIterator<Item = Vec2<i32>> + '_ {
106        self.pending_chunks.par_keys().copied()
107    }
108
109    pub fn cancel_if_pending(&mut self, key: Vec2<i32>) {
110        if let Some(cancel) = self.pending_chunks.remove(&key) {
111            cancel.store(true, Ordering::Relaxed);
112            self.metrics.chunks_canceled.inc();
113        }
114    }
115
116    pub fn cancel_all(&mut self) {
117        let metrics = Arc::clone(&self.metrics);
118        self.pending_chunks.drain().for_each(|(_, cancel)| {
119            cancel.store(true, Ordering::Relaxed);
120            metrics.chunks_canceled.inc();
121        });
122    }
123}