1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
use hashbrown::{HashMap, HashSet};
use specs::{Join, ReadExpect, ReadStorage, System, WriteExpect, WriteStorage};

use crate::{
    ChunkInterests, ChunkProtocol, ChunkRequestsComp, ChunkStatus, Chunks, ClientFilter, IDComp,
    Mesher, Message, MessageQueue, MessageType, Pipeline, Vec2, WorldConfig,
};

pub struct ChunkRequestsSystem;

impl<'a> System<'a> for ChunkRequestsSystem {
    type SystemData = (
        ReadExpect<'a, Chunks>,
        ReadExpect<'a, WorldConfig>,
        WriteExpect<'a, ChunkInterests>,
        WriteExpect<'a, Pipeline>,
        WriteExpect<'a, Mesher>,
        WriteExpect<'a, MessageQueue>,
        ReadStorage<'a, IDComp>,
        WriteStorage<'a, ChunkRequestsComp>,
    );

    // 1. Go through all chunk requests, specifically under the `requested` set.
    // 2. If chunk DNE, Add the chunks to be generated in the pipeline.
    // 3. Move the chunk from the `requested` set to the `processed` set.
    // 4. Otherwise, send directly to the client.
    fn run(&mut self, data: Self::SystemData) {
        let (chunks, config, mut interests, mut pipeline, mut mesher, mut queue, ids, mut requests) =
            data;

        let max_response_per_tick = config.max_response_per_tick;

        let mut to_send: HashMap<String, HashSet<Vec2<i32>>> = HashMap::new();

        for (id, requests) in (&ids, &mut requests).join() {
            let mut to_add_back_to_requested = HashSet::new();

            for coords in requests.requests.drain(..) {
                // If the chunk is actually ready, send to client.
                if chunks.is_chunk_ready(&coords) {
                    let mut clients_to_send = to_send.remove(&id.0).unwrap_or_default();

                    if clients_to_send.len() >= max_response_per_tick {
                        to_send.insert(id.0.clone(), clients_to_send);
                        to_add_back_to_requested.insert(coords);
                        continue;
                    }

                    // Add the chunk to the list of chunks to send to the client.
                    clients_to_send.insert(coords.clone());

                    to_send.insert(id.0.clone(), clients_to_send);

                    interests.add(&id.0, &coords);

                    continue;
                }

                if !interests.has_interests(&coords) {
                    chunks
                        .light_traversed_chunks(&coords)
                        .into_iter()
                        .for_each(|coords| {
                            // If this chunk is DNE or if this chunk is still in the pipeline, we re-add it to the pipeline.
                            if chunks.raw(&coords).is_none()
                                || matches!(
                                    chunks.raw(&coords).unwrap().status,
                                    ChunkStatus::Generating(_)
                                )
                            {
                                pipeline.add_chunk(&coords, false);
                            }
                            // If this chunk is in the meshing stage, we re-add it to the mesher.
                            else if let Some(chunk) = chunks.raw(&coords) {
                                if matches!(chunk.status, ChunkStatus::Meshing) {
                                    mesher.add_chunk(&coords, false);
                                }
                            }
                        });
                }

                interests.add(&id.0, &coords);
            }

            // Add the chunks back to the requested set.
            requests.requests.extend(to_add_back_to_requested);
        }

        // Send the chunks to the client.
        to_send.into_iter().for_each(|(id, coords)| {
            let chunks: Vec<ChunkProtocol> = coords
                .into_iter()
                .map(|coords| {
                    let chunk = chunks.get(&coords).unwrap();

                    chunk.to_model(true, true, 0..config.sub_chunks as u32)
                })
                .collect();

            let message = Message::new(&MessageType::Load).chunks(&chunks).build();
            queue.push((message, ClientFilter::Direct(id)))
        })
    }
}