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 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
#[cfg(not(feature = "worldgen"))]
use crate::test_world::World;
use crate::{chunk_serialize::ChunkSendEntry, client::Client, Settings};
use common::{
comp::{Pos, Presence},
event::EventBus,
};
use common_ecs::{Job, Origin, Phase, System};
use common_net::msg::{CompressedData, ServerGeneral};
use common_state::TerrainChanges;
use rayon::prelude::*;
use specs::{Entities, Join, Read, ReadExpect, ReadStorage};
use std::sync::Arc;
#[cfg(feature = "worldgen")] use world::World;
/// This systems sends new chunks to clients as well as changes to existing
/// chunks
#[derive(Default)]
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
ReadExpect<'a, Arc<World>>,
Read<'a, Settings>,
Read<'a, TerrainChanges>,
ReadExpect<'a, EventBus<ChunkSendEntry>>,
ReadStorage<'a, Pos>,
ReadStorage<'a, Presence>,
ReadStorage<'a, Client>,
);
const NAME: &'static str = "terrain_sync";
const ORIGIN: Origin = Origin::Server;
const PHASE: Phase = Phase::Create;
fn run(
_job: &mut Job<Self>,
(
entities,
world,
server_settings,
terrain_changes,
chunk_send_bus,
positions,
presences,
clients,
): Self::SystemData,
) {
let max_view_distance = server_settings.max_view_distance.unwrap_or(u32::MAX);
#[cfg(feature = "worldgen")]
let world_size = world.sim().get_size();
#[cfg(not(feature = "worldgen"))]
let world_size = world.map_size_lg().chunks().as_();
let (presences_position_entities, _) = super::terrain::prepare_player_presences(
world_size,
max_view_distance,
&entities,
&positions,
&presences,
&clients,
);
let real_max_view_distance =
super::terrain::convert_to_loaded_vd(u32::MAX, max_view_distance);
// Sync changed chunks
terrain_changes.modified_chunks.par_iter().for_each_init(
|| chunk_send_bus.emitter(),
|chunk_send_emitter, &chunk_key| {
// We only have to check players inside the maximum view distance of the server
// of our own position.
//
// We start by partitioning by X, finding only entities in chunks within the X
// range of us. These are guaranteed in bounds due to restrictions on max view
// distance (namely: the square of any chunk coordinate plus the max view
// distance along both axes must fit in an i32).
let min_chunk_x = chunk_key.x - real_max_view_distance;
let max_chunk_x = chunk_key.x + real_max_view_distance;
let start = presences_position_entities
.partition_point(|((pos, _), _)| i32::from(pos.x) < min_chunk_x);
// NOTE: We *could* just scan forward until we hit the end, but this way we save
// a comparison in the inner loop, since also needs to check the
// list length. We could also save some time by starting from
// start rather than end, but the hope is that this way the
// compiler (and machine) can reorder things so both ends are
// fetched in parallel; since the vast majority of the time both fetched
// elements should already be in cache, this should not use any
// extra memory bandwidth.
//
// TODO: Benchmark and figure out whether this is better in practice than just
// scanning forward.
let end = presences_position_entities
.partition_point(|((pos, _), _)| i32::from(pos.x) < max_chunk_x);
let interior = &presences_position_entities[start..end];
interior
.iter()
.filter(|((player_chunk_pos, player_vd_sqr), _)| {
super::terrain::chunk_in_vd(*player_chunk_pos, *player_vd_sqr, chunk_key)
})
.for_each(|(_, entity)| {
chunk_send_emitter.emit(ChunkSendEntry {
entity: *entity,
chunk_key,
});
});
},
);
// TODO: Don't send all changed blocks to all clients
// Sync changed blocks
if !terrain_changes.modified_blocks.is_empty() {
let mut lazy_msg = None;
for (_, client) in (&presences, &clients).join() {
if lazy_msg.is_none() {
lazy_msg = Some(client.prepare(ServerGeneral::TerrainBlockUpdates(
CompressedData::compress(&terrain_changes.modified_blocks, 1),
)));
}
lazy_msg.as_ref().map(|msg| client.send_prepared(msg));
}
}
}
}