Make terrain compression a checkbox instead of a bandwidth (throughput?) heuristic.

This commit is contained in:
Avi Weinstock 2021-05-01 14:28:20 -04:00
parent f0824af80a
commit be39054767
17 changed files with 185 additions and 80 deletions

View File

@ -53,6 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Custom map markers can be placed now - Custom map markers can be placed now
- Fundamentals/prototype for wiring system - Fundamentals/prototype for wiring system
- Mountain peak and lake markers on the map - Mountain peak and lake markers on the map
- There's now a checkbox in the graphics tab to opt-in to receiving lossily-compressed terrain colors.
### Changed ### Changed

View File

@ -77,6 +77,7 @@
"hud.settings.fullscreen_mode.exclusive": "Exclusive", "hud.settings.fullscreen_mode.exclusive": "Exclusive",
"hud.settings.fullscreen_mode.borderless": "Borderless", "hud.settings.fullscreen_mode.borderless": "Borderless",
"hud.settings.particles": "Particles", "hud.settings.particles": "Particles",
"hud.settings.lossy_terrain_compression": "Lossy terrain compression",
"hud.settings.resolution": "Resolution", "hud.settings.resolution": "Resolution",
"hud.settings.bit_depth": "Bit Depth", "hud.settings.bit_depth": "Bit Depth",
"hud.settings.refresh_rate": "Refresh Rate", "hud.settings.refresh_rate": "Refresh Rate",

View File

@ -800,7 +800,10 @@ impl Client {
| ClientGeneral::RefundSkill(_) | ClientGeneral::RefundSkill(_)
| ClientGeneral::RequestSiteInfo(_) | ClientGeneral::RequestSiteInfo(_)
| ClientGeneral::UnlockSkillGroup(_) | ClientGeneral::UnlockSkillGroup(_)
| ClientGeneral::RequestPlayerPhysics { .. } => &mut self.in_game_stream, | ClientGeneral::RequestPlayerPhysics { .. }
| ClientGeneral::RequestLossyTerrainCompression { .. } => {
&mut self.in_game_stream
},
//Only in game, terrain //Only in game, terrain
ClientGeneral::TerrainChunkRequest { .. } => &mut self.terrain_stream, ClientGeneral::TerrainChunkRequest { .. } => &mut self.terrain_stream,
//Always possible //Always possible
@ -820,6 +823,12 @@ impl Client {
}) })
} }
pub fn request_lossy_terrain_compression(&mut self, lossy_terrain_compression: bool) {
self.send_msg(ClientGeneral::RequestLossyTerrainCompression {
lossy_terrain_compression,
})
}
fn send_msg<S>(&mut self, msg: S) fn send_msg<S>(&mut self, msg: S)
where where
S: Into<ClientMsg>, S: Into<ClientMsg>,

View File

@ -84,6 +84,9 @@ pub enum ClientGeneral {
RequestPlayerPhysics { RequestPlayerPhysics {
server_authoritative: bool, server_authoritative: bool,
}, },
RequestLossyTerrainCompression {
lossy_terrain_compression: bool,
},
} }
impl ClientMsg { impl ClientMsg {
@ -121,7 +124,8 @@ impl ClientMsg {
| ClientGeneral::RefundSkill(_) | ClientGeneral::RefundSkill(_)
| ClientGeneral::RequestSiteInfo(_) | ClientGeneral::RequestSiteInfo(_)
| ClientGeneral::UnlockSkillGroup(_) | ClientGeneral::UnlockSkillGroup(_)
| ClientGeneral::RequestPlayerPhysics { .. } => { | ClientGeneral::RequestPlayerPhysics { .. }
| ClientGeneral::RequestLossyTerrainCompression { .. } => {
c_type == ClientType::Game && presence.is_some() c_type == ClientType::Game && presence.is_some()
}, },
//Always possible //Always possible

View File

@ -15,7 +15,7 @@ pub use self::{
server::{ server::{
CharacterInfo, DisconnectReason, InviteAnswer, Notification, PlayerInfo, PlayerListUpdate, CharacterInfo, DisconnectReason, InviteAnswer, Notification, PlayerInfo, PlayerListUpdate,
RegisterError, SerializedTerrainChunk, ServerGeneral, ServerInfo, ServerInit, ServerMsg, RegisterError, SerializedTerrainChunk, ServerGeneral, ServerInfo, ServerInit, ServerMsg,
ServerRegisterAnswer, TERRAIN_LOW_BANDWIDTH, ServerRegisterAnswer,
}, },
world_msg::WorldMapMsg, world_msg::WorldMapMsg,
}; };

View File

@ -73,13 +73,9 @@ pub enum SerializedTerrainChunk {
TriPng(WireChonk<TriPngEncoding<false>, WidePacking<true>, TerrainChunkMeta, TerrainChunkSize>), TriPng(WireChonk<TriPngEncoding<false>, WidePacking<true>, TerrainChunkMeta, TerrainChunkSize>),
} }
/// If someone has less than this number of bytes per second of bandwidth, spend
/// more CPU generating a smaller encoding of terrain data.
pub const TERRAIN_LOW_BANDWIDTH: f32 = 500_000.0;
impl SerializedTerrainChunk { impl SerializedTerrainChunk {
pub fn via_heuristic(chunk: &TerrainChunk, low_bandwidth: bool) -> Self { pub fn via_heuristic(chunk: &TerrainChunk, lossy_compression: bool) -> Self {
if low_bandwidth && (chunk.get_max_z() - chunk.get_min_z() <= 128) { if lossy_compression && (chunk.get_max_z() - chunk.get_min_z() <= 128) {
Self::quadpng(chunk) Self::quadpng(chunk)
} else { } else {
Self::deflate(chunk) Self::deflate(chunk)

View File

@ -34,8 +34,8 @@ pub struct NetworkRequestMetrics {
pub chunks_request_dropped: IntCounter, pub chunks_request_dropped: IntCounter,
pub chunks_served_from_memory: IntCounter, pub chunks_served_from_memory: IntCounter,
pub chunks_generation_triggered: IntCounter, pub chunks_generation_triggered: IntCounter,
pub chunks_served_lo_bandwidth: IntCounter, pub chunks_served_lossy: IntCounter,
pub chunks_served_hi_bandwidth: IntCounter, pub chunks_served_lossless: IntCounter,
} }
pub struct ChunkGenMetrics { pub struct ChunkGenMetrics {
@ -189,29 +189,27 @@ impl NetworkRequestMetrics {
"chunks_generation_triggered", "chunks_generation_triggered",
"number of all chunks that were requested and needs to be generated", "number of all chunks that were requested and needs to be generated",
))?; ))?;
let chunks_served_lo_bandwidth = IntCounter::with_opts(Opts::new( let chunks_served_lossy = IntCounter::with_opts(Opts::new(
"chunks_served_lo_bandwidth", "chunks_served_lossy",
"number of chunks that were sent with compression setting by the low bandwidth \ "number of chunks that were sent with lossy compression requested",
heuristic",
))?; ))?;
let chunks_served_hi_bandwidth = IntCounter::with_opts(Opts::new( let chunks_served_lossless = IntCounter::with_opts(Opts::new(
"chunks_served_hi_bandwidth", "chunks_served_lossless",
"number of chunks that were sent with compression setting by the high bandwidth \ "number of chunks that were sent with lossless compression requested",
heuristic",
))?; ))?;
registry.register(Box::new(chunks_request_dropped.clone()))?; registry.register(Box::new(chunks_request_dropped.clone()))?;
registry.register(Box::new(chunks_served_from_memory.clone()))?; registry.register(Box::new(chunks_served_from_memory.clone()))?;
registry.register(Box::new(chunks_generation_triggered.clone()))?; registry.register(Box::new(chunks_generation_triggered.clone()))?;
registry.register(Box::new(chunks_served_lo_bandwidth.clone()))?; registry.register(Box::new(chunks_served_lossy.clone()))?;
registry.register(Box::new(chunks_served_hi_bandwidth.clone()))?; registry.register(Box::new(chunks_served_lossless.clone()))?;
Ok(Self { Ok(Self {
chunks_request_dropped, chunks_request_dropped,
chunks_served_from_memory, chunks_served_from_memory,
chunks_generation_triggered, chunks_generation_triggered,
chunks_served_lo_bandwidth, chunks_served_lossy,
chunks_served_hi_bandwidth, chunks_served_lossless,
}) })
} }
} }

View File

@ -9,6 +9,7 @@ use vek::*;
pub struct Presence { pub struct Presence {
pub view_distance: u32, pub view_distance: u32,
pub kind: PresenceKind, pub kind: PresenceKind,
pub lossy_terrain_compression: bool,
} }
impl Presence { impl Presence {
@ -16,6 +17,7 @@ impl Presence {
Self { Self {
view_distance, view_distance,
kind, kind,
lossy_terrain_compression: false,
} }
} }
} }

View File

@ -253,6 +253,11 @@ impl Sys {
setting.client_optin = server_authoritative; setting.client_optin = server_authoritative;
} }
}, },
ClientGeneral::RequestLossyTerrainCompression {
lossy_terrain_compression,
} => {
presence.lossy_terrain_compression = lossy_terrain_compression;
},
_ => tracing::error!("not a client_in_game msg"), _ => tracing::error!("not a client_in_game msg"),
} }
Ok(()) Ok(())

View File

@ -6,9 +6,7 @@ use common::{
vol::RectVolSize, vol::RectVolSize,
}; };
use common_ecs::{Job, Origin, ParMode, Phase, System}; use common_ecs::{Job, Origin, ParMode, Phase, System};
use common_net::msg::{ use common_net::msg::{ClientGeneral, SerializedTerrainChunk, ServerGeneral};
ClientGeneral, SerializedTerrainChunk, ServerGeneral, TERRAIN_LOW_BANDWIDTH,
};
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
use specs::{Entities, Join, ParJoin, Read, ReadExpect, ReadStorage}; use specs::{Entities, Join, ParJoin, Read, ReadExpect, ReadStorage};
use tracing::{debug, trace}; use tracing::{debug, trace};
@ -79,21 +77,17 @@ impl<'a> System<'a> for Sys {
match terrain.get_key_arc(key) { match terrain.get_key_arc(key) {
Some(chunk) => { Some(chunk) => {
network_metrics.chunks_served_from_memory.inc(); network_metrics.chunks_served_from_memory.inc();
if let Some(participant) = &client.participant { client.send(ServerGeneral::TerrainChunkUpdate {
let low_bandwidth = key,
participant.bandwidth() < TERRAIN_LOW_BANDWIDTH; chunk: Ok(SerializedTerrainChunk::via_heuristic(
client.send(ServerGeneral::TerrainChunkUpdate { &chunk,
key, presence.lossy_terrain_compression,
chunk: Ok(SerializedTerrainChunk::via_heuristic( )),
&chunk, })?;
low_bandwidth, if presence.lossy_terrain_compression {
)), network_metrics.chunks_served_lossy.inc();
})?; } else {
if low_bandwidth { network_metrics.chunks_served_lossless.inc();
network_metrics.chunks_served_lo_bandwidth.inc();
} else {
network_metrics.chunks_served_hi_bandwidth.inc();
}
} }
}, },
None => { None => {

View File

@ -14,7 +14,7 @@ use common::{
LoadoutBuilder, SkillSetBuilder, LoadoutBuilder, SkillSetBuilder,
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use common_net::msg::{SerializedTerrainChunk, ServerGeneral, TERRAIN_LOW_BANDWIDTH}; use common_net::msg::{SerializedTerrainChunk, ServerGeneral};
use common_state::TerrainChanges; use common_state::TerrainChanges;
use comp::Behavior; use comp::Behavior;
use specs::{Join, Read, ReadExpect, ReadStorage, Write, WriteExpect}; use specs::{Join, Read, ReadExpect, ReadStorage, Write, WriteExpect};
@ -43,34 +43,35 @@ impl LazyTerrainMessage {
&mut self, &mut self,
network_metrics: &NetworkRequestMetrics, network_metrics: &NetworkRequestMetrics,
client: &Client, client: &Client,
presence: &Presence,
chunk_key: &vek::Vec2<i32>, chunk_key: &vek::Vec2<i32>,
generate_chunk: F, generate_chunk: F,
) -> Result<(), A> { ) -> Result<(), A> {
if let Some(participant) = &client.participant { let lazy_msg = if presence.lossy_terrain_compression {
let low_bandwidth = participant.bandwidth() < TERRAIN_LOW_BANDWIDTH; &mut self.lazy_msg_lo
let lazy_msg = if low_bandwidth { } else {
&mut self.lazy_msg_lo &mut self.lazy_msg_hi
} else { };
&mut self.lazy_msg_hi if lazy_msg.is_none() {
}; *lazy_msg = Some(client.prepare(ServerGeneral::TerrainChunkUpdate {
if lazy_msg.is_none() { key: *chunk_key,
*lazy_msg = Some(client.prepare(ServerGeneral::TerrainChunkUpdate { chunk: Ok(match generate_chunk() {
key: *chunk_key, Ok(chunk) => SerializedTerrainChunk::via_heuristic(
chunk: Ok(match generate_chunk() { &chunk,
Ok(chunk) => SerializedTerrainChunk::via_heuristic(&chunk, low_bandwidth), presence.lossy_terrain_compression,
Err(e) => return Err(e), ),
}), Err(e) => return Err(e),
})); }),
} }));
lazy_msg.as_ref().map(|ref msg| {
let _ = client.send_prepared(&msg);
if low_bandwidth {
network_metrics.chunks_served_lo_bandwidth.inc();
} else {
network_metrics.chunks_served_hi_bandwidth.inc();
}
});
} }
lazy_msg.as_ref().map(|ref msg| {
let _ = client.send_prepared(&msg);
if presence.lossy_terrain_compression {
network_metrics.chunks_served_lossy.inc();
} else {
network_metrics.chunks_served_lossless.inc();
}
});
Ok(()) Ok(())
} }
} }
@ -293,9 +294,13 @@ impl<'a> System<'a> for Sys {
if adjusted_dist_sqr <= presence.view_distance.pow(2) { if adjusted_dist_sqr <= presence.view_distance.pow(2) {
lazy_msg lazy_msg
.prepare_and_send::<!, _>(&network_metrics, &client, &key, || { .prepare_and_send::<!, _>(
Ok(&*chunk) &network_metrics,
}) &client,
&presence,
&key,
|| Ok(&*chunk),
)
.into_ok(); .into_ok();
} }
}); });

View File

@ -35,11 +35,13 @@ impl<'a> System<'a> for Sys {
for (presence, pos, client) in (&presences, &positions, &clients).join() { for (presence, pos, client) in (&presences, &positions, &clients).join() {
if super::terrain::chunk_in_vd(pos.0, *chunk_key, &terrain, presence.view_distance) if super::terrain::chunk_in_vd(pos.0, *chunk_key, &terrain, presence.view_distance)
{ {
if let Err(()) = if let Err(()) = lazy_msg.prepare_and_send(
lazy_msg.prepare_and_send(&network_metrics, &client, chunk_key, || { &network_metrics,
terrain.get_key(*chunk_key).ok_or(()) &client,
}) &presence,
{ chunk_key,
|| terrain.get_key(*chunk_key).ok_or(()),
) {
break 'chunk; break 'chunk;
} }
} }

View File

@ -82,6 +82,8 @@ widget_ids! {
// //
particles_button, particles_button,
particles_label, particles_label,
lossy_terrain_compression_button,
lossy_terrain_compression_label,
// //
fullscreen_button, fullscreen_button,
fullscreen_label, fullscreen_label,
@ -874,6 +876,42 @@ impl<'a> Widget for Video<'a> {
events.push(ToggleParticlesEnabled(particles_enabled)); events.push(ToggleParticlesEnabled(particles_enabled));
} }
// Lossy terrain compression
Text::new(
&self
.localized_strings
.get("hud.settings.lossy_terrain_compression"),
)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.right_from(state.ids.particles_label, 64.0)
.color(TEXT_COLOR)
.set(state.ids.lossy_terrain_compression_label, ui);
let lossy_terrain_compression = ToggleButton::new(
self.global_state
.settings
.graphics
.lossy_terrain_compression,
self.imgs.checkbox,
self.imgs.checkbox_checked,
)
.w_h(18.0, 18.0)
.right_from(state.ids.lossy_terrain_compression_label, 10.0)
.hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
.press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
.set(state.ids.lossy_terrain_compression_button, ui);
if self
.global_state
.settings
.graphics
.lossy_terrain_compression
!= lossy_terrain_compression
{
events.push(ToggleLossyTerrainCompression(lossy_terrain_compression));
}
// Resolution // Resolution
let resolutions: Vec<[u16; 2]> = state let resolutions: Vec<[u16; 2]> = state
.video_modes .video_modes

View File

@ -88,9 +88,13 @@ impl SessionState {
scene scene
.camera_mut() .camera_mut()
.set_fov_deg(global_state.settings.graphics.fov); .set_fov_deg(global_state.settings.graphics.fov);
client {
.borrow_mut() let mut client = client.borrow_mut();
.request_player_physics(global_state.settings.gameplay.player_physics_behavior); client.request_player_physics(global_state.settings.gameplay.player_physics_behavior);
client.request_lossy_terrain_compression(
global_state.settings.graphics.lossy_terrain_compression,
);
}
let hud = Hud::new(global_state, &client.borrow()); let hud = Hud::new(global_state, &client.borrow());
let walk_forward_dir = scene.camera().forward_xy(); let walk_forward_dir = scene.camera().forward_xy();
let walk_right_dir = scene.camera().right_xy(); let walk_right_dir = scene.camera().right_xy();

View File

@ -71,6 +71,7 @@ pub enum Graphics {
ChangeFullscreenMode(FullScreenSettings), ChangeFullscreenMode(FullScreenSettings),
ToggleParticlesEnabled(bool), ToggleParticlesEnabled(bool),
ToggleLossyTerrainCompression(bool),
AdjustWindowSize([u16; 2]), AdjustWindowSize([u16; 2]),
ResetGraphicsSettings, ResetGraphicsSettings,
@ -329,6 +330,13 @@ impl SettingsChange {
Graphics::ToggleParticlesEnabled(particles_enabled) => { Graphics::ToggleParticlesEnabled(particles_enabled) => {
settings.graphics.particles_enabled = particles_enabled; settings.graphics.particles_enabled = particles_enabled;
}, },
Graphics::ToggleLossyTerrainCompression(lossy_terrain_compression) => {
settings.graphics.lossy_terrain_compression = lossy_terrain_compression;
session_state
.client
.borrow_mut()
.request_lossy_terrain_compression(lossy_terrain_compression);
},
Graphics::AdjustWindowSize(new_size) => { Graphics::AdjustWindowSize(new_size) => {
global_state.window.set_size(new_size.into()); global_state.window.set_size(new_size.into());
settings.graphics.window_size = new_size; settings.graphics.window_size = new_size;

View File

@ -32,6 +32,7 @@ pub struct GraphicsSettings {
pub view_distance: u32, pub view_distance: u32,
pub sprite_render_distance: u32, pub sprite_render_distance: u32,
pub particles_enabled: bool, pub particles_enabled: bool,
pub lossy_terrain_compression: bool,
pub figure_lod_render_distance: u32, pub figure_lod_render_distance: u32,
pub max_fps: Fps, pub max_fps: Fps,
pub fov: u16, pub fov: u16,
@ -50,6 +51,7 @@ impl Default for GraphicsSettings {
view_distance: 10, view_distance: 10,
sprite_render_distance: 100, sprite_render_distance: 100,
particles_enabled: true, particles_enabled: true,
lossy_terrain_compression: false,
figure_lod_render_distance: 300, figure_lod_render_distance: 300,
max_fps: Fps::Max(60), max_fps: Fps::Max(60),
fov: 70, fov: 70,

View File

@ -563,7 +563,7 @@ fn main() {
.unwrap(), .unwrap(),
)); ));
const SKIP_DEFLATE_2_5: bool = true; const SKIP_DEFLATE_2_5: bool = false;
const SKIP_DYNA: bool = true; const SKIP_DYNA: bool = true;
const SKIP_IMAGECHONK: bool = true; const SKIP_IMAGECHONK: bool = true;
const SKIP_MIXED: bool = true; const SKIP_MIXED: bool = true;
@ -701,6 +701,42 @@ fn main() {
("deflate4chonk", (deflate4chonk_post - deflate4chonk_pre).subsec_nanos()), ("deflate4chonk", (deflate4chonk_post - deflate4chonk_pre).subsec_nanos()),
("deflate5chonk", (deflate5chonk_post - deflate5chonk_pre).subsec_nanos()), ("deflate5chonk", (deflate5chonk_post - deflate5chonk_pre).subsec_nanos()),
]); ]);
{
let bucket = z_buckets
.entry("deflate2")
.or_default()
.entry(chunk.get_max_z() - chunk.get_min_z())
.or_insert((0, 0.0));
bucket.0 += 1;
bucket.1 += (deflate2chonk_post - deflate2chonk_pre).subsec_nanos() as f32;
}
{
let bucket = z_buckets
.entry("deflate3")
.or_default()
.entry(chunk.get_max_z() - chunk.get_min_z())
.or_insert((0, 0.0));
bucket.0 += 1;
bucket.1 += (deflate3chonk_post - deflate3chonk_pre).subsec_nanos() as f32;
}
{
let bucket = z_buckets
.entry("deflate4")
.or_default()
.entry(chunk.get_max_z() - chunk.get_min_z())
.or_insert((0, 0.0));
bucket.0 += 1;
bucket.1 += (deflate4chonk_post - deflate4chonk_pre).subsec_nanos() as f32;
}
{
let bucket = z_buckets
.entry("deflate5")
.or_default()
.entry(chunk.get_max_z() - chunk.get_min_z())
.or_insert((0, 0.0));
bucket.0 += 1;
bucket.1 += (deflate5chonk_post - deflate5chonk_pre).subsec_nanos() as f32;
}
} }
if !SKIP_DYNA { if !SKIP_DYNA {
@ -886,7 +922,7 @@ fn main() {
("tripngaverage", (tripngaverage_post - tripngaverage_pre).subsec_nanos()), ("tripngaverage", (tripngaverage_post - tripngaverage_pre).subsec_nanos()),
("tripngconst", (tripngconst_post - tripngconst_pre).subsec_nanos()), ("tripngconst", (tripngconst_post - tripngconst_pre).subsec_nanos()),
]); ]);
{ if false {
let bucket = z_buckets let bucket = z_buckets
.entry("quadpngquarttall") .entry("quadpngquarttall")
.or_default() .or_default()
@ -906,7 +942,7 @@ fn main() {
bucket.1 += bucket.1 +=
(quadpngquartwide_post - quadpngquartwide_pre).subsec_nanos() as f32; (quadpngquartwide_post - quadpngquartwide_pre).subsec_nanos() as f32;
} }
if true { if false {
let bucket = z_buckets let bucket = z_buckets
.entry("tripngaverage") .entry("tripngaverage")
.or_default() .or_default()