mc_core/world/
source.rs

1use std::ops::{Deref, DerefMut};
2use std::sync::{Arc, RwLock, Mutex};
3use std::time::{Instant, Duration};
4use std::error::Error;
5use std::fmt::{Debug, Formatter};
6
7use crossbeam_channel::{Sender, Receiver, unbounded, bounded};
8use hecs::EntityBuilder;
9use thiserror::Error;
10
11use super::chunk::{Chunk, ChunkHeight};
12use crate::world::chunk::ChunkStatus;
13use crate::world::level::LevelEnv;
14use crate::block::BlockState;
15
16
17/// Common level source error.
18#[derive(Error, Debug)]
19pub enum LevelSourceError {
20    #[error("The required chunk position is not supported by the source.")]
21    UnsupportedChunkPosition,
22    #[error("Chunk loading is not supported by the targeted source.")]
23    UnsupportedChunkLoad,
24    #[error("Chunk saving is not supported by the targeted source.")]
25    UnsupportedChunkSave,
26    #[error("Custom source error: {0}")]
27    Custom(Box<dyn Error + Send>)
28}
29
30impl LevelSourceError {
31    pub fn new_custom(err: impl Error + Send + 'static) -> Self {
32        Self::Custom(Box::new(err))
33    }
34}
35
36
37/// Level loader trait to implement for each different loader such as
38/// disk or generator loaders, combine these two types of loaders to
39/// save and load change in a chunk to avoid generating twice.
40pub trait LevelSource {
41
42    /// Request loading of the chunk at the given position. If you return an error, you must
43    /// return back the given `ChunkInfo` together with the `LevelSourceError`. If you return
44    /// `Ok(())` **you must** give a result later when calling `poll_chunk`. **This operation
45    /// must be non-blocking.**
46    fn request_chunk_load(&mut self, req: ChunkLoadRequest) -> Result<(), (LevelSourceError, ChunkLoadRequest)> {
47        Err((LevelSourceError::UnsupportedChunkLoad, req))
48    }
49
50    /// Poll the next loaded chunk that is ready to be inserted into the level's chunk storage.
51    /// Every requested load chunk `request_chunk_load` method that returned `Ok(())` should
52    /// return some some result here, even if it's an error. **This operation must be
53    /// non-blocking.**
54    fn poll_chunk(&mut self) -> Option<Result<ProtoChunk, (LevelSourceError, ChunkLoadRequest)>> {
55        None
56    }
57
58    /// Request saving of the chunk at the given position. **This operation must be non-blocking.**
59    #[allow(unused_variables)]
60    fn request_chunk_save(&mut self, req: ChunkSaveRequest) -> Result<(), LevelSourceError> {
61        Err(LevelSourceError::UnsupportedChunkSave)
62    }
63
64}
65
66
67/// This structure is constructed by levels and passed to `LevelSource` when requesting for
68/// chunk loading, the chunk must be constructed from the given data.
69#[derive(Clone, Debug)]
70pub struct ChunkLoadRequest {
71    pub env: Arc<LevelEnv>,
72    pub height: ChunkHeight,
73    pub cx: i32,
74    pub cz: i32,
75}
76
77impl ChunkLoadRequest {
78
79    /// Build a chunk from this chunk info.
80    pub fn build_chunk(&self) -> Chunk {
81        Chunk::new(Arc::clone(&self.env), self.height, self.cx, self.cz)
82    }
83
84    pub fn build_proto_chunk(&self) -> ProtoChunk {
85        ProtoChunk {
86            inner: Box::new(self.build_chunk()),
87            proto_entities: Vec::new(),
88            dirty: false
89        }
90    }
91
92}
93
94#[derive(Clone)]
95pub struct ChunkSaveRequest {
96    pub cx: i32,
97    pub cz: i32,
98    pub chunk: Arc<RwLock<Chunk>>
99}
100
101
102/// A temporary chunk structure used to add entity builders that will be added to the level's ECS
103/// later in sync when the source actually returns it.
104pub struct ProtoChunk {
105    /// A boxed chunk, the inner chunk is intentionally boxed because proto chunks are made
106    /// to be moved and sent between threads.
107    pub(super) inner: Box<Chunk>,
108    /// Proto entities that will be built and added to the
109    pub(super) proto_entities: Vec<(EntityBuilder, Vec<usize>)>,
110    /// This boolean indicates when the proto chunk must be saved after being added to the level.
111    pub dirty: bool
112}
113
114impl ProtoChunk {
115
116    /// Add an entity builder to this proto chunk, this builder will be added to the level when
117    /// building the actual `Chunk`. **You must** ensure that this entity contains a `BaseEntity`
118    /// component with an `entity_type` supported by the level's environment.
119    ///
120    /// This method also return the index of this entity within the proto chunk, this can be
121    /// used to add passengers to this entity or make this entity ride another one.
122    pub fn add_proto_entity(&mut self, entity_builder: EntityBuilder) -> usize {
123        let idx = self.proto_entities.len();
124        self.proto_entities.push((entity_builder, Vec::new()));
125        idx
126    }
127
128    pub fn add_proto_entity_passengers(&mut self, host_index: usize, passenger_index: usize) {
129        self.proto_entities[host_index].1.push(passenger_index);
130    }
131
132}
133
134impl Deref for ProtoChunk {
135    type Target = Chunk;
136    fn deref(&self) -> &Self::Target {
137        &self.inner
138    }
139}
140
141impl DerefMut for ProtoChunk {
142    fn deref_mut(&mut self) -> &mut Self::Target {
143        &mut self.inner
144    }
145}
146
147impl Debug for ProtoChunk {
148    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
149        f.debug_struct("ProtoChunk")
150            .field("dirty", &self.dirty)
151            .field("proto_entities", &self.proto_entities.len())
152            .finish_non_exhaustive()
153    }
154}
155
156
157/// Dummy chunk loader which do nothing.
158pub struct NullLevelSource;
159impl LevelSource for NullLevelSource {}
160
161
162/// A load or generate LevelSource variant.
163///
164/// This can be used for exemple with an anvil region source as the loader and a super-flat
165/// generator as generator. In this case the generator will be called only for chunks that
166/// are not supported by the loader (returned UnsupportedChunkPosition error).
167pub struct LoadOrGenLevelSource<L, G> {
168    loader: L,
169    generator: G
170}
171
172impl<L, G> LoadOrGenLevelSource<L, G>
173where
174    L: LevelSource,
175    G: LevelSource,
176{
177
178    /// Construct a new load or generate `LevelSource`. You should ensure that the given
179    /// sources does not return `UnsupportedOperation` for `request_chunk_load`. If they does,
180    /// this source will also return this type of error when requesting chunk load and then
181    /// will be unusable.
182    pub fn new(loader: L, generator: G) -> Self {
183        Self {
184            loader,
185            generator
186        }
187    }
188
189}
190
191impl<L, G> LevelSource for LoadOrGenLevelSource<L, G>
192where
193    L: LevelSource,
194    G: LevelSource,
195{
196
197    fn request_chunk_load(&mut self, req: ChunkLoadRequest) -> Result<(), (LevelSourceError, ChunkLoadRequest)> {
198        match self.loader.request_chunk_load(req) {
199            Err((LevelSourceError::UnsupportedChunkPosition, info)) => {
200                // If the loader does not support this chunk, directly request the generator.
201                self.generator.request_chunk_load(info)
202            }
203            Err(e) => Err(e),
204            _ => Ok(())
205        }
206    }
207
208    fn poll_chunk(&mut self) -> Option<Result<ProtoChunk, (LevelSourceError, ChunkLoadRequest)>> {
209
210        // We check the loader first.
211        while let Some(res) = self.loader.poll_chunk() {
212            match res {
213                // If the source error is an unsupported position, just delegate to the generator.
214                Err((LevelSourceError::UnsupportedChunkPosition, chunk_info)) => {
215                    match self.generator.request_chunk_load(chunk_info) {
216                        Err(e) => return Some(Err(e)),
217                        Ok(_) => {}
218                    }
219                },
220                // If this is not an unsupported position, Ok or other Err, just return it.
221                res => return Some(res)
222            }
223        }
224
225        // Then we poll chunks from the generator.
226        let mut res = self.generator.poll_chunk();
227        if let Some(Ok(ref mut proto_chunk)) = res {
228            // Because this proto chunk was just generated, mark it as dirty in
229            // order to save it once added to the level.
230            proto_chunk.dirty = true;
231        }
232        res
233
234    }
235
236    fn request_chunk_save(&mut self, req: ChunkSaveRequest) -> Result<(), LevelSourceError> {
237        self.loader.request_chunk_save(req)
238    }
239
240}
241
242
243/// A trait to implement for level generators, this trait provides a synchronous method to
244/// generate a specific chunk. This trait is not valid for methods expecting a `LevelSource`,
245/// to do this you need to wrap it into `LevelGeneratorSource`, this structure will clone your
246/// generator in any given workers count and run them asynchronously.
247pub trait LevelGenerator {
248    fn generate(&mut self, info: ChunkLoadRequest) -> Result<ProtoChunk, (LevelSourceError, ChunkLoadRequest)>;
249}
250
251
252/// A trait used by `LevelGeneratorSource` in order to build a specific `LevelGenerator` in each
253/// worker thread, this allows the generator to be thread unsafe. For this specific use, this
254/// implies that this builder should be send and sync for.
255pub trait LevelGeneratorBuilder {
256    type Generator: LevelGenerator;
257    fn build(&mut self) -> Self::Generator;
258}
259
260
261/// A wrapper for `LevelGenerator` that implements `LevelSource` to provide asynchronous level
262/// generation. This wrapper dispatches incoming chunk request into the given number of worker
263/// threads.
264pub struct WorkerGenLevelSource {
265    request_sender: Sender<ChunkLoadRequest>,
266    result_receiver: Receiver<Result<ProtoChunk, (LevelSourceError, ChunkLoadRequest)>>,
267}
268
269impl WorkerGenLevelSource {
270
271    pub fn new<B>(generator_builder: B, workers_count: usize) -> Self
272    where
273        B: LevelGeneratorBuilder + Send + Sync + 'static
274    {
275
276        let (
277            request_sender,
278            request_receiver
279        ) = unbounded();
280
281        let (
282            result_sender,
283            result_receiver
284        ) = bounded(workers_count * 128);
285
286        let generator_builder = Arc::new(Mutex::new(generator_builder));
287
288        for i in 0..workers_count {
289
290            let request_receiver = request_receiver.clone();
291            let result_sender = result_sender.clone();
292            let generator_builder = Arc::clone(&generator_builder);
293
294            std::thread::Builder::new()
295                .name(format!("Level generator worker #{}", i))
296                .spawn(move || {
297                    let worker = {
298                        LevelGeneratorSourceWorker {
299                            generator: generator_builder.lock().unwrap().build(),
300                            request_receiver,
301                            result_sender,
302                            total_count: 0,
303                            total_duration: Duration::default(),
304                        }
305                    };
306                    worker.run()
307                })
308                .unwrap();
309
310        }
311
312        Self {
313            request_sender,
314            result_receiver
315        }
316
317    }
318
319}
320
321impl LevelSource for WorkerGenLevelSource {
322
323    fn request_chunk_load(&mut self, req: ChunkLoadRequest) -> Result<(), (LevelSourceError, ChunkLoadRequest)> {
324        // SAFETY: Unwrap should be safe because the channel is unbounded.
325        self.request_sender.send(req).unwrap();
326        Ok(())
327    }
328
329    fn poll_chunk(&mut self) -> Option<Result<ProtoChunk, (LevelSourceError, ChunkLoadRequest)>> {
330        self.result_receiver.try_recv().ok()
331    }
332
333}
334
335/// Internal thread structure used by `LevelGeneratorSource`.
336struct LevelGeneratorSourceWorker<G> {
337    generator: G,
338    request_receiver: Receiver<ChunkLoadRequest>,
339    result_sender: Sender<Result<ProtoChunk, (LevelSourceError, ChunkLoadRequest)>>,
340    total_count: u32,
341    total_duration: Duration
342}
343
344impl<G> LevelGeneratorSourceWorker<G>
345where
346    G: LevelGenerator
347{
348
349    fn run(mut self) {
350        loop {
351            // TODO: println!("[{}] Waiting...", std::thread::current().name().unwrap());
352            match self.request_receiver.recv() {
353                Ok(chunk_info) => {
354                    let begin = Instant::now();
355                    let res = self.generator.generate(chunk_info);
356                    self.total_duration += begin.elapsed();
357                    self.total_count += 1;
358                    // TODO: println!("[{}] Average time: {:?}", std::thread::current().name().unwrap(), self.total_duration / self.total_count);
359                    if let Err(_) = self.result_sender.send(res) {
360                        break
361                    }
362                },
363                Err(_) => break
364            }
365        }
366    }
367
368}
369
370
371/// A primitive super-flat generator that only generate the terrain from given layers,
372/// no structure is generated.
373#[derive(Debug, Clone)]
374pub struct SuperFlatGenerator {
375    layers: Vec<(&'static BlockState, i32, u32)>
376}
377
378impl SuperFlatGenerator {
379
380    pub fn new() -> Self {
381        Self {
382            layers: Vec::new()
383        }
384    }
385
386    pub fn add_layer(&mut self, state: &'static BlockState, y: i32, height: u32) {
387        self.layers.push((state, y, height));
388    }
389
390}
391
392impl LevelGenerator for SuperFlatGenerator {
393
394    fn generate(&mut self, info: ChunkLoadRequest) -> Result<ProtoChunk, (LevelSourceError, ChunkLoadRequest)> {
395        let mut chunk = info.build_proto_chunk();
396        for &(state, y, height) in &self.layers {
397            for y in y..(y + height as i32) {
398                // TODO: This algorithm is not optimized, we can optimize it if we add
399                //  a "fill_blocks" method in "Chunk".
400                for x in 0..16 {
401                    for z in 0..16 {
402                        let _ = chunk.set_block(x, y, z, state);
403                    }
404                }
405            }
406        }
407        chunk.set_status(ChunkStatus::Full);
408        Ok(chunk)
409    }
410
411}