veloren_server/sys/
terrain_sync.rs

1#[cfg(not(feature = "worldgen"))]
2use crate::test_world::World;
3use crate::{Settings, chunk_serialize::ChunkSendEntry, client::Client};
4use common::{
5    comp::{Pos, Presence},
6    event::EventBus,
7};
8use common_ecs::{Job, Origin, Phase, System};
9use common_net::msg::{CompressedData, ServerGeneral};
10use common_state::TerrainChanges;
11use rayon::prelude::*;
12use specs::{Entities, Join, Read, ReadExpect, ReadStorage};
13use std::sync::Arc;
14#[cfg(feature = "worldgen")] use world::World;
15
16/// This systems sends new chunks to clients as well as changes to existing
17/// chunks
18#[derive(Default)]
19pub struct Sys;
20impl<'a> System<'a> for Sys {
21    type SystemData = (
22        Entities<'a>,
23        ReadExpect<'a, Arc<World>>,
24        Read<'a, Settings>,
25        Read<'a, TerrainChanges>,
26        ReadExpect<'a, EventBus<ChunkSendEntry>>,
27        ReadStorage<'a, Pos>,
28        ReadStorage<'a, Presence>,
29        ReadStorage<'a, Client>,
30    );
31
32    const NAME: &'static str = "terrain_sync";
33    const ORIGIN: Origin = Origin::Server;
34    const PHASE: Phase = Phase::Create;
35
36    fn run(
37        _job: &mut Job<Self>,
38        (
39            entities,
40            world,
41            server_settings,
42            terrain_changes,
43            chunk_send_bus,
44            positions,
45            presences,
46            clients,
47        ): Self::SystemData,
48    ) {
49        let max_view_distance = server_settings.max_view_distance.unwrap_or(u32::MAX);
50        #[cfg(feature = "worldgen")]
51        let world_size = world.sim().get_size();
52        #[cfg(not(feature = "worldgen"))]
53        let world_size = world.map_size_lg().chunks().as_();
54        let (presences_position_entities, _) = super::terrain::prepare_player_presences(
55            world_size,
56            max_view_distance,
57            &entities,
58            &positions,
59            &presences,
60            &clients,
61        );
62        let real_max_view_distance =
63            super::terrain::convert_to_loaded_vd(u32::MAX, max_view_distance);
64
65        // Sync changed chunks
66        terrain_changes.modified_chunks.par_iter().for_each_init(
67            || chunk_send_bus.emitter(),
68            |chunk_send_emitter, &chunk_key| {
69                // We only have to check players inside the maximum view distance of the server
70                // of our own position.
71                //
72                // We start by partitioning by X, finding only entities in chunks within the X
73                // range of us.  These are guaranteed in bounds due to restrictions on max view
74                // distance (namely: the square of any chunk coordinate plus the max view
75                // distance along both axes must fit in an i32).
76                let min_chunk_x = chunk_key.x - real_max_view_distance;
77                let max_chunk_x = chunk_key.x + real_max_view_distance;
78                let start = presences_position_entities
79                    .partition_point(|((pos, _), _)| i32::from(pos.x) < min_chunk_x);
80                // NOTE: We *could* just scan forward until we hit the end, but this way we save
81                // a comparison in the inner loop, since also needs to check the
82                // list length.  We could also save some time by starting from
83                // start rather than end, but the hope is that this way the
84                // compiler (and machine) can reorder things so both ends are
85                // fetched in parallel; since the vast majority of the time both fetched
86                // elements should already be in cache, this should not use any
87                // extra memory bandwidth.
88                //
89                // TODO: Benchmark and figure out whether this is better in practice than just
90                // scanning forward.
91                let end = presences_position_entities
92                    .partition_point(|((pos, _), _)| i32::from(pos.x) < max_chunk_x);
93                let interior = &presences_position_entities[start..end];
94                interior
95                    .iter()
96                    .filter(|((player_chunk_pos, player_vd_sqr), _)| {
97                        super::terrain::chunk_in_vd(*player_chunk_pos, *player_vd_sqr, chunk_key)
98                    })
99                    .for_each(|(_, entity)| {
100                        chunk_send_emitter.emit(ChunkSendEntry {
101                            entity: *entity,
102                            chunk_key,
103                        });
104                    });
105            },
106        );
107
108        // TODO: Don't send all changed blocks to all clients
109        // Sync changed blocks
110        if !terrain_changes.modified_blocks.is_empty() {
111            let mut lazy_msg = None;
112            for (_, client) in (&presences, &clients).join() {
113                if lazy_msg.is_none() {
114                    lazy_msg = Some(client.prepare(ServerGeneral::TerrainBlockUpdates(
115                        CompressedData::compress(&terrain_changes.modified_blocks, 1),
116                    )));
117                }
118                lazy_msg.as_ref().map(|msg| client.send_prepared(msg));
119            }
120        }
121    }
122}