mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
massivly switch clock algorithm.
- before we had a Clock that tried to average multiple ticks and predict the next sleep. This system is massivly bugged. a) We know exactly how long the busy time took, so we dont need to predict anything in the first place b) Preduction was totally unrealistic after a single lag spike c) When a very slow tick happens, we dont benefit from 10 fast ticks. - Instead we just try to keep the tick time exact what we expect. If we can't manage a constant tick time because we are to slow, the systems have to "catch" this via the `dt` anyway.
This commit is contained in:
parent
a8ac051f33
commit
e4e5c6e55b
@ -65,6 +65,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Switched to a Whittaker map for better tree spawning patterns
|
||||
- Switched to procedural snow cover on trees
|
||||
- Significantly improved terrain generation performance
|
||||
- Significantly stabilized the game clock, to produce more "constant" TPS
|
||||
|
||||
### Removed
|
||||
|
||||
|
24
Cargo.lock
generated
24
Cargo.lock
generated
@ -4593,16 +4593,7 @@ checksum = "6f70411ba0d11c4aee042b1c73e68086322a293f8038c53ac95d36319c91ec19"
|
||||
dependencies = [
|
||||
"tracing-core",
|
||||
"tracing-subscriber",
|
||||
"tracy-client 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracy-client"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "650c280019cd1a841752d50f6a834216a2c8a810aeb18bdd48054be23cacd8a6"
|
||||
dependencies = [
|
||||
"tracy-client-sys 0.9.0",
|
||||
"tracy-client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4611,16 +4602,7 @@ version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb214203bffe8cefe9bc0dfc12140f85e8d59b42e1f52f696071fdf8595e066a"
|
||||
dependencies = [
|
||||
"tracy-client-sys 0.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracy-client-sys"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c07e4636dad390858d247cf2633eaea90e67d7e21151cd476bbfd070d321279f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tracy-client-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4907,7 +4889,7 @@ dependencies = [
|
||||
"specs-idvs",
|
||||
"sum_type",
|
||||
"tracing",
|
||||
"tracy-client 0.8.0",
|
||||
"tracy-client",
|
||||
"vek 0.12.0",
|
||||
]
|
||||
|
||||
|
@ -26,7 +26,7 @@ fn main() {
|
||||
info!("Starting chat-cli...");
|
||||
|
||||
// Set up an fps clock.
|
||||
let mut clock = Clock::start();
|
||||
let mut clock = Clock::new(Duration::from_millis(1000 / TPS));
|
||||
|
||||
println!("Enter your username");
|
||||
let username = read_input();
|
||||
@ -71,11 +71,7 @@ fn main() {
|
||||
client.send_chat(msg)
|
||||
}
|
||||
|
||||
let events = match client.tick(
|
||||
comp::ControllerInputs::default(),
|
||||
clock.get_last_delta(),
|
||||
|_| {},
|
||||
) {
|
||||
let events = match client.tick(comp::ControllerInputs::default(), clock.dt(), |_| {}) {
|
||||
Ok(events) => events,
|
||||
Err(err) => {
|
||||
error!("Error: {:?}", err);
|
||||
@ -104,6 +100,6 @@ fn main() {
|
||||
client.cleanup();
|
||||
|
||||
// Wait for the next tick.
|
||||
clock.tick(Duration::from_millis(1000 / TPS));
|
||||
clock.tick();
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ num-traits = "0.2"
|
||||
num-derive = "0.3"
|
||||
|
||||
# Tracy
|
||||
tracy-client = { version = "0.8.0", optional = true }
|
||||
tracy-client = { version = "0.9.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3"
|
||||
|
@ -1,63 +1,170 @@
|
||||
use crate::span;
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
const CLOCK_SMOOTHING: f64 = 0.9;
|
||||
|
||||
/// This Clock tries to make this tick a constant time by sleeping the rest of
|
||||
/// the tick
|
||||
/// - if we actually took less time than we planned: sleep and return planned
|
||||
/// time
|
||||
/// - if we ran behind: don't sleep and return actual time
|
||||
/// We DON'T do any fancy averaging of the deltas for tick for 2 reasons:
|
||||
/// - all Systems have to work based on `dt` and we cannot assume that this is
|
||||
/// const through all ticks
|
||||
/// - when we have a slow tick, a lag, it doesn't help that we have 10 fast
|
||||
/// ticks directly afterwards
|
||||
/// We return a smoothed version for display only!
|
||||
pub struct Clock {
|
||||
/// This is the dt that the Clock tries to archive with each call of tick.
|
||||
target_dt: Duration,
|
||||
/// last time `tick` was called
|
||||
last_sys_time: Instant,
|
||||
last_delta: Option<Duration>,
|
||||
running_average_delta: f64,
|
||||
compensation: f64,
|
||||
/// will be calculated in `tick` returns the dt used by the next iteration
|
||||
/// of the main loop
|
||||
last_dt: Duration,
|
||||
/// summed up `last_dt`
|
||||
total_tick_time: Duration,
|
||||
// Stats only
|
||||
// stored as millis in u16 to save space. if it's more than u16::MAX (16s) we have other
|
||||
// problems
|
||||
last_dts_millis: VecDeque<u16>,
|
||||
last_dts_millis_sorted: Vec<u16>,
|
||||
stats: ClockStats,
|
||||
}
|
||||
|
||||
pub struct ClockStats {
|
||||
/// busy_dt is the part of the last tick that we didn't sleep.
|
||||
/// e.g. the total tick is 33ms, including 25ms sleeping. then this returns
|
||||
/// 8ms
|
||||
pub last_busy_dt: Duration,
|
||||
/// avg over the last NUMBER_OF_OLD_DELTAS_KEPT ticks
|
||||
pub average_tps: f64,
|
||||
/// = 50% percentile
|
||||
pub median_tps: f64,
|
||||
/// lowest 10% of the frames
|
||||
pub percentile_90_tps: f64,
|
||||
/// lowest 5% of the frames
|
||||
pub percentile_95_tps: f64,
|
||||
/// lowest 1% of the frames
|
||||
pub percentile_99_tps: f64,
|
||||
}
|
||||
|
||||
const NUMBER_OF_OLD_DELTAS_KEPT: usize = 100;
|
||||
|
||||
impl Clock {
|
||||
pub fn start() -> Self {
|
||||
pub fn new(target_dt: Duration) -> Self {
|
||||
Self {
|
||||
target_dt,
|
||||
last_sys_time: Instant::now(),
|
||||
last_delta: None,
|
||||
running_average_delta: 0.0,
|
||||
compensation: 1.0,
|
||||
last_dt: target_dt,
|
||||
total_tick_time: Duration::default(),
|
||||
last_dts_millis: VecDeque::with_capacity(NUMBER_OF_OLD_DELTAS_KEPT),
|
||||
last_dts_millis_sorted: Vec::with_capacity(NUMBER_OF_OLD_DELTAS_KEPT),
|
||||
stats: ClockStats::new(&Vec::new(), target_dt),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_tps(&self) -> f64 { 1.0 / self.running_average_delta }
|
||||
pub fn set_target_dt(&mut self, target_dt: Duration) { self.target_dt = target_dt; }
|
||||
|
||||
pub fn get_last_delta(&self) -> Duration {
|
||||
self.last_delta.unwrap_or_else(|| Duration::new(0, 0))
|
||||
}
|
||||
pub fn stats(&self) -> &ClockStats { &self.stats }
|
||||
|
||||
pub fn get_avg_delta(&self) -> Duration { Duration::from_secs_f64(self.running_average_delta) }
|
||||
pub fn dt(&self) -> Duration { self.last_dt }
|
||||
|
||||
pub fn tick(&mut self, tgt: Duration) {
|
||||
/// Do not modify without asking @xMAC94x first!
|
||||
pub fn tick(&mut self) {
|
||||
span!(_guard, "tick", "Clock::tick");
|
||||
let delta = Instant::now().duration_since(self.last_sys_time);
|
||||
let current_sys_time = Instant::now();
|
||||
let busy_delta = current_sys_time.duration_since(self.last_sys_time);
|
||||
// Maintain TPS
|
||||
self.last_dts_millis_sorted = self.last_dts_millis.iter().copied().collect();
|
||||
self.last_dts_millis_sorted.sort_unstable();
|
||||
self.stats = ClockStats::new(&self.last_dts_millis_sorted, busy_delta);
|
||||
|
||||
// Attempt to sleep to fill the gap.
|
||||
if let Some(sleep_dur) = tgt.checked_sub(delta) {
|
||||
if self.running_average_delta != 0.0 {
|
||||
self.compensation =
|
||||
(self.compensation + (tgt.as_secs_f64() / self.running_average_delta) - 1.0)
|
||||
.max(0.0)
|
||||
}
|
||||
|
||||
let sleep_secs = sleep_dur.as_secs_f64() * self.compensation;
|
||||
if sleep_secs > 0.0 {
|
||||
thread::sleep(Duration::from_secs_f64(sleep_secs));
|
||||
}
|
||||
if let Some(sleep_dur) = self.target_dt.checked_sub(busy_delta) {
|
||||
Clock::sleep(current_sys_time, sleep_dur)
|
||||
}
|
||||
|
||||
let delta = Instant::now().duration_since(self.last_sys_time);
|
||||
let after_sleep_sys_time = Instant::now();
|
||||
self.last_dt = after_sleep_sys_time.duration_since(self.last_sys_time);
|
||||
if self.last_dts_millis.len() >= NUMBER_OF_OLD_DELTAS_KEPT {
|
||||
self.last_dts_millis.pop_front();
|
||||
}
|
||||
self.last_dts_millis
|
||||
.push_back((self.last_dt.as_millis() as u16).min(std::u16::MAX));
|
||||
self.total_tick_time += self.last_dt;
|
||||
self.last_sys_time = after_sleep_sys_time;
|
||||
}
|
||||
|
||||
self.last_sys_time = Instant::now();
|
||||
self.last_delta = Some(delta);
|
||||
self.running_average_delta = if self.running_average_delta == 0.0 {
|
||||
delta.as_secs_f64()
|
||||
/// try to do a high precision sleep.
|
||||
/// When this `fn` returns it SHOULD be before+dur as accurate is possible.
|
||||
/// Returns the after sleep Instant.
|
||||
fn sleep(before: Instant, dur: Duration) {
|
||||
const BUSY_WAITING_TRIGGER_DUR: Duration = Duration::from_millis(2);
|
||||
#[cfg(not(windows))]
|
||||
const BUSY_WAITING_TIME: Duration = Duration::from_nanos(125_000);
|
||||
#[cfg(windows)]
|
||||
const BUSY_WAITING_TIME: Duration = Duration::from_nanos(500_000);
|
||||
|
||||
// If we have more than BUSY_TRIGGER_DUR on our sleep bucket, do sleep for
|
||||
// (1-BUSY_WAITING_TIME) time and do some busy waiting. If we are on high load,
|
||||
// don't try such fancy precision increasing
|
||||
if dur > BUSY_WAITING_TRIGGER_DUR {
|
||||
let first_sleep_dur = dur - BUSY_WAITING_TIME;
|
||||
thread::sleep(first_sleep_dur);
|
||||
let target_time = before + dur;
|
||||
span!(_guard, "tick", "Clock::busy_wait");
|
||||
while target_time > Instant::now() {
|
||||
//busy waiting
|
||||
std::sync::atomic::spin_loop_hint();
|
||||
}
|
||||
} else {
|
||||
CLOCK_SMOOTHING * self.running_average_delta
|
||||
+ (1.0 - CLOCK_SMOOTHING) * delta.as_secs_f64()
|
||||
};
|
||||
thread::sleep(dur);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClockStats {
|
||||
fn new(sorted: &[u16], last_busy_dt: Duration) -> Self {
|
||||
const NANOS_PER_SEC: f64 = Duration::from_secs(1).as_nanos() as f64;
|
||||
const NANOS_PER_MILLI: f64 = Duration::from_millis(1).as_nanos() as f64;
|
||||
|
||||
let len = sorted.len();
|
||||
let average_millis = sorted.iter().fold(0, |a, x| a + *x as u32) / len.max(1) as u32;
|
||||
|
||||
let average_tps = NANOS_PER_SEC / (average_millis as f64 * NANOS_PER_MILLI);
|
||||
let (median_tps, percentile_90_tps, percentile_95_tps, percentile_99_tps) = if len
|
||||
>= NUMBER_OF_OLD_DELTAS_KEPT
|
||||
{
|
||||
let median_millis = sorted[len / 2];
|
||||
let percentile_90_millis = sorted[(NUMBER_OF_OLD_DELTAS_KEPT as f32 * 0.1) as usize];
|
||||
let percentile_95_millis = sorted[(NUMBER_OF_OLD_DELTAS_KEPT as f32 * 0.05) as usize];
|
||||
let percentile_99_millis = sorted[(NUMBER_OF_OLD_DELTAS_KEPT as f32 * 0.01) as usize];
|
||||
|
||||
let median_tps = NANOS_PER_SEC / (median_millis as f64 * NANOS_PER_MILLI);
|
||||
let percentile_90_tps = NANOS_PER_SEC / (percentile_90_millis as f64 * NANOS_PER_MILLI);
|
||||
let percentile_95_tps = NANOS_PER_SEC / (percentile_95_millis as f64 * NANOS_PER_MILLI);
|
||||
let percentile_99_tps = NANOS_PER_SEC / (percentile_99_millis as f64 * NANOS_PER_MILLI);
|
||||
(
|
||||
median_tps,
|
||||
percentile_90_tps,
|
||||
percentile_95_tps,
|
||||
percentile_99_tps,
|
||||
)
|
||||
} else {
|
||||
let avg_tps = NANOS_PER_SEC / last_busy_dt.as_nanos() as f64;
|
||||
(avg_tps, avg_tps, avg_tps, avg_tps)
|
||||
};
|
||||
|
||||
Self {
|
||||
last_busy_dt,
|
||||
average_tps,
|
||||
median_tps,
|
||||
percentile_90_tps,
|
||||
percentile_95_tps,
|
||||
percentile_99_tps,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -140,19 +140,22 @@ fn main() -> io::Result<()> {
|
||||
let mut shutdown_coordinator = ShutdownCoordinator::new(Arc::clone(&sigusr1_signal));
|
||||
|
||||
// Set up an fps clock
|
||||
let mut clock = Clock::start();
|
||||
let mut clock = Clock::new(Duration::from_millis(1000 / TPS));
|
||||
// Wait for a tick so we don't start with a zero dt
|
||||
// TODO: consider integrating this into Clock::start?
|
||||
clock.tick(Duration::from_millis(1000 / TPS));
|
||||
|
||||
loop {
|
||||
#[cfg(feature = "tracy")]
|
||||
common::util::tracy_client::finish_continuous_frame!();
|
||||
#[cfg(feature = "tracy")]
|
||||
let frame = common::util::tracy_client::start_noncontinuous_frame!("work");
|
||||
|
||||
// Terminate the server if instructed to do so by the shutdown coordinator
|
||||
if shutdown_coordinator.check(&mut server, &settings) {
|
||||
break;
|
||||
}
|
||||
|
||||
let events = server
|
||||
.tick(Input::default(), clock.get_last_delta())
|
||||
.tick(Input::default(), clock.dt())
|
||||
.expect("Failed to tick server");
|
||||
|
||||
for event in events {
|
||||
@ -165,8 +168,6 @@ fn main() -> io::Result<()> {
|
||||
|
||||
// Clean up the server after a tick.
|
||||
server.cleanup();
|
||||
#[cfg(feature = "tracy")]
|
||||
common::util::tracy_client::finish_continuous_frame!();
|
||||
|
||||
if let Some(tui) = tui.as_ref() {
|
||||
match tui.msg_r.try_recv() {
|
||||
@ -194,8 +195,10 @@ fn main() -> io::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tracy")]
|
||||
drop(frame);
|
||||
// Wait for the next tick.
|
||||
clock.tick(Duration::from_millis(1000 / TPS));
|
||||
clock.tick();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -114,7 +114,7 @@ impl MusicChannel {
|
||||
|
||||
/// Maintain the fader attached to this channel. If the channel is not
|
||||
/// fading, no action is taken.
|
||||
pub fn maintain(&mut self, dt: f32) {
|
||||
pub fn maintain(&mut self, dt: std::time::Duration) {
|
||||
if self.state == ChannelState::Fading {
|
||||
self.fader.update(dt);
|
||||
self.sink.set_volume(self.fader.get_volume());
|
||||
|
@ -1,11 +1,13 @@
|
||||
//! Controls volume transitions for Audio Channels
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
/// Faders are attached to channels with initial and target volumes as well as a
|
||||
/// transition time.
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
pub struct Fader {
|
||||
length: f32,
|
||||
running_time: f32,
|
||||
length: Duration,
|
||||
running_time: Duration,
|
||||
volume_from: f32,
|
||||
volume_to: f32,
|
||||
is_running: bool,
|
||||
@ -21,19 +23,19 @@ pub enum FadeDirection {
|
||||
fn lerp(t: f32, a: f32, b: f32) -> f32 { (1.0 - t) * a + t * b }
|
||||
|
||||
impl Fader {
|
||||
pub fn fade(length: f32, volume_from: f32, volume_to: f32) -> Self {
|
||||
pub fn fade(length: Duration, volume_from: f32, volume_to: f32) -> Self {
|
||||
Self {
|
||||
length,
|
||||
running_time: 0.0,
|
||||
running_time: Duration::default(),
|
||||
volume_from,
|
||||
volume_to,
|
||||
is_running: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fade_in(time: f32, volume_to: f32) -> Self { Self::fade(time, 0.0, volume_to) }
|
||||
pub fn fade_in(time: Duration, volume_to: f32) -> Self { Self::fade(time, 0.0, volume_to) }
|
||||
|
||||
pub fn fade_out(time: f32, volume_from: f32) -> Self { Self::fade(time, volume_from, 0.0) }
|
||||
pub fn fade_out(time: Duration, volume_from: f32) -> Self { Self::fade(time, volume_from, 0.0) }
|
||||
|
||||
/// Used to update the `target` volume of the fader when the max or min
|
||||
/// volume changes. This occurs when the player changes their in-game
|
||||
@ -62,7 +64,7 @@ impl Fader {
|
||||
}
|
||||
|
||||
/// Called each tick to update the volume and state
|
||||
pub fn update(&mut self, dt: f32) {
|
||||
pub fn update(&mut self, dt: std::time::Duration) {
|
||||
if self.is_running {
|
||||
self.running_time += dt;
|
||||
if self.running_time >= self.length {
|
||||
@ -74,7 +76,7 @@ impl Fader {
|
||||
|
||||
pub fn get_volume(&self) -> f32 {
|
||||
lerp(
|
||||
self.running_time / self.length,
|
||||
self.running_time.as_nanos() as f32 / self.length.as_nanos() as f32,
|
||||
self.volume_from,
|
||||
self.volume_to,
|
||||
)
|
||||
@ -87,8 +89,8 @@ impl Fader {
|
||||
impl Default for Fader {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
length: 0.0,
|
||||
running_time: 0.0,
|
||||
length: Duration::default(),
|
||||
running_time: Duration::default(),
|
||||
volume_from: 0.0,
|
||||
volume_to: 1.0,
|
||||
is_running: false,
|
||||
@ -102,14 +104,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn fade_direction_in() {
|
||||
let fader = Fader::fade_in(10.0, 0.0);
|
||||
let fader = Fader::fade_in(Duration::from_secs(10), 0.0);
|
||||
|
||||
assert_eq!(fader.direction(), FadeDirection::In);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fade_direction_out() {
|
||||
let fader = Fader::fade_out(10.0, 1.0);
|
||||
let fader = Fader::fade_out(Duration::from_secs(10), 1.0);
|
||||
|
||||
assert_eq!(fader.direction(), FadeDirection::Out);
|
||||
}
|
||||
@ -117,10 +119,10 @@ mod tests {
|
||||
#[test]
|
||||
#[allow(clippy::float_cmp)] // TODO: Pending review in #587
|
||||
fn fade_out_completes() {
|
||||
let mut fader = Fader::fade_out(10.0, 1.0);
|
||||
let mut fader = Fader::fade_out(Duration::from_secs(10), 1.0);
|
||||
|
||||
// Run for the full duration
|
||||
fader.update(10.0);
|
||||
fader.update(Duration::from_secs(10));
|
||||
|
||||
assert_eq!(fader.get_volume(), 0.0);
|
||||
assert!(fader.is_finished());
|
||||
@ -128,32 +130,32 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn update_target_volume_fading_out_when_currently_above() {
|
||||
let mut fader = Fader::fade_out(20.0, 1.0);
|
||||
let mut fader = Fader::fade_out(Duration::from_secs(20), 1.0);
|
||||
|
||||
// After 0.1s, the fader should still be close to 1.0
|
||||
fader.update(0.1);
|
||||
fader.update(Duration::from_millis(100));
|
||||
|
||||
// Reduce volume to 0.4. We are currently above that.
|
||||
fader.update_target_volume(0.4);
|
||||
|
||||
// The volume should immediately reduce to < 0.4 on the next update
|
||||
fader.update(0.1);
|
||||
fader.update(Duration::from_millis(100));
|
||||
|
||||
assert!(fader.get_volume() < 0.4)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_target_volume_fading_out_when_currently_below() {
|
||||
let mut fader = Fader::fade_out(10.0, 0.8);
|
||||
let mut fader = Fader::fade_out(Duration::from_secs(10), 0.8);
|
||||
|
||||
// After 9s, the fader should be close to 0
|
||||
fader.update(9.0);
|
||||
fader.update(Duration::from_secs(9));
|
||||
|
||||
// Notify of a volume increase to 1.0. We are already far below that.
|
||||
fader.update_target_volume(1.0);
|
||||
|
||||
// The fader should be unaffected by the new value, and continue dropping
|
||||
fader.update(0.1);
|
||||
fader.update(Duration::from_millis(100));
|
||||
|
||||
assert!(fader.get_volume() < 0.2);
|
||||
}
|
||||
@ -161,16 +163,16 @@ mod tests {
|
||||
#[test]
|
||||
#[allow(clippy::float_cmp)] // TODO: Pending review in #587
|
||||
fn update_target_volume_fading_in_when_currently_above() {
|
||||
let mut fader = Fader::fade_in(10.0, 1.0);
|
||||
let mut fader = Fader::fade_in(Duration::from_secs(10), 1.0);
|
||||
|
||||
// After 9s, the fader should be close to 1.0
|
||||
fader.update(9.0);
|
||||
fader.update(Duration::from_secs(9));
|
||||
|
||||
// Reduce volume to 0.4. We are currently above that.
|
||||
fader.update_target_volume(0.4);
|
||||
|
||||
// Run out the fader. It's volume should be 0.4
|
||||
fader.update(1.0);
|
||||
fader.update(Duration::from_secs(1));
|
||||
|
||||
assert_eq!(fader.get_volume(), 0.4);
|
||||
}
|
||||
@ -178,10 +180,10 @@ mod tests {
|
||||
#[test]
|
||||
#[allow(clippy::float_cmp)] // TODO: Pending review in #587
|
||||
fn update_target_volume_fading_in_when_currently_below() {
|
||||
let mut fader = Fader::fade_in(20.0, 1.0);
|
||||
let mut fader = Fader::fade_in(Duration::from_secs(20), 1.0);
|
||||
|
||||
// After 0.1s, the fader should still be close to 0.0
|
||||
fader.update(0.1);
|
||||
fader.update(Duration::from_millis(100));
|
||||
|
||||
// Reduce volume to 0.4. The volume_to should be reduced accordingly.
|
||||
fader.update_target_volume(0.4);
|
||||
|
@ -9,6 +9,7 @@ pub mod soundcache;
|
||||
use channel::{MusicChannel, MusicChannelTag, SfxChannel};
|
||||
use fader::Fader;
|
||||
use soundcache::SoundCache;
|
||||
use std::time::Duration;
|
||||
use tracing::warn;
|
||||
|
||||
use common::assets;
|
||||
@ -84,7 +85,7 @@ impl AudioFrontend {
|
||||
}
|
||||
|
||||
/// Drop any unused music channels, and update their faders
|
||||
pub fn maintain(&mut self, dt: f32) {
|
||||
pub fn maintain(&mut self, dt: Duration) {
|
||||
self.music_channels.retain(|c| !c.is_done());
|
||||
|
||||
for channel in self.music_channels.iter_mut() {
|
||||
@ -124,11 +125,13 @@ impl AudioFrontend {
|
||||
|
||||
if existing_channel.get_tag() != next_channel_tag {
|
||||
// Fade the existing channel out. It will be removed when the fade completes.
|
||||
existing_channel.set_fader(Fader::fade_out(2.0, self.music_volume));
|
||||
existing_channel
|
||||
.set_fader(Fader::fade_out(Duration::from_secs(2), self.music_volume));
|
||||
|
||||
let mut next_music_channel = MusicChannel::new(&audio_device);
|
||||
|
||||
next_music_channel.set_fader(Fader::fade_in(12.0, self.music_volume));
|
||||
next_music_channel
|
||||
.set_fader(Fader::fade_in(Duration::from_secs(12), self.music_volume));
|
||||
|
||||
self.music_channels.push(next_music_channel);
|
||||
}
|
||||
|
@ -746,7 +746,7 @@ impl Hud {
|
||||
// pulse time for pulsating elements
|
||||
self.pulse = self.pulse + dt.as_secs_f32();
|
||||
// FPS
|
||||
let fps = global_state.clock.get_tps();
|
||||
let fps = global_state.clock.stats().average_tps;
|
||||
let version = common::util::DISPLAY_VERSION_LONG.clone();
|
||||
|
||||
if self.show.ingame {
|
||||
|
@ -63,7 +63,7 @@ impl GlobalState {
|
||||
self.window.needs_refresh_resize();
|
||||
}
|
||||
|
||||
pub fn maintain(&mut self, dt: f32) { self.audio.maintain(dt); }
|
||||
pub fn maintain(&mut self, dt: std::time::Duration) { self.audio.maintain(dt); }
|
||||
|
||||
#[cfg(feature = "singleplayer")]
|
||||
pub fn paused(&self) -> bool {
|
||||
|
@ -185,7 +185,7 @@ fn main() {
|
||||
profile,
|
||||
window,
|
||||
settings,
|
||||
clock: Clock::start(),
|
||||
clock: Clock::new(std::time::Duration::from_millis(30)),
|
||||
info_message: None,
|
||||
#[cfg(feature = "singleplayer")]
|
||||
singleplayer: None,
|
||||
|
@ -155,7 +155,7 @@ impl PlayState for CharSelectionState {
|
||||
|
||||
match self.client.borrow_mut().tick(
|
||||
comp::ControllerInputs::default(),
|
||||
global_state.clock.get_last_delta(),
|
||||
global_state.clock.dt(),
|
||||
|_| {},
|
||||
) {
|
||||
Ok(events) => {
|
||||
|
@ -211,7 +211,7 @@ impl PlayState for MainMenuState {
|
||||
// Maintain the UI.
|
||||
for event in self
|
||||
.main_menu_ui
|
||||
.maintain(global_state, global_state.clock.get_last_delta())
|
||||
.maintain(global_state, global_state.clock.dt())
|
||||
{
|
||||
match event {
|
||||
MainMenuEvent::LoginAttempt {
|
||||
|
@ -168,12 +168,13 @@ fn handle_main_events_cleared(
|
||||
if !exit {
|
||||
// Wait for the next tick.
|
||||
span!(_guard, "Main thread sleep");
|
||||
global_state.clock.tick(Duration::from_millis(
|
||||
global_state.clock.set_target_dt(Duration::from_millis(
|
||||
1000 / global_state.settings.graphics.max_fps as u64,
|
||||
));
|
||||
global_state.clock.tick();
|
||||
|
||||
span!(_guard, "Maintain global state");
|
||||
// Maintain global state.
|
||||
global_state.maintain(global_state.clock.get_last_delta().as_secs_f32());
|
||||
global_state.maintain(global_state.clock.dt());
|
||||
}
|
||||
}
|
||||
|
@ -631,7 +631,7 @@ impl PlayState for SessionState {
|
||||
let right = self.scene.camera().right();
|
||||
let dir = right * axis_right + forward * axis_up;
|
||||
|
||||
let dt = global_state.clock.get_last_delta().as_secs_f32();
|
||||
let dt = global_state.clock.dt().as_secs_f32();
|
||||
if self.freefly_vel.magnitude_squared() > 0.01 {
|
||||
let new_vel = self.freefly_vel
|
||||
- self.freefly_vel.normalized() * (FREEFLY_DAMPING * dt);
|
||||
@ -668,11 +668,7 @@ impl PlayState for SessionState {
|
||||
// Runs if either in a multiplayer server or the singleplayer server is unpaused
|
||||
if !global_state.paused() {
|
||||
// Perform an in-game tick.
|
||||
match self.tick(
|
||||
global_state.clock.get_avg_delta(),
|
||||
global_state,
|
||||
&mut outcomes,
|
||||
) {
|
||||
match self.tick(global_state.clock.dt(), global_state, &mut outcomes) {
|
||||
Ok(TickAction::Continue) => {}, // Do nothing
|
||||
Ok(TickAction::Disconnect) => return PlayStateResult::Pop, // Go to main menu
|
||||
Err(err) => {
|
||||
@ -697,8 +693,8 @@ impl PlayState for SessionState {
|
||||
.gameplay
|
||||
.toggle_debug
|
||||
.then(|| DebugInfo {
|
||||
tps: global_state.clock.get_tps(),
|
||||
frame_time: global_state.clock.get_avg_delta(),
|
||||
tps: global_state.clock.stats().average_tps,
|
||||
frame_time: global_state.clock.stats().last_busy_dt,
|
||||
ping_ms: self.client.borrow().get_ping_ms_rolling_avg(),
|
||||
coordinates: self
|
||||
.client
|
||||
@ -741,7 +737,7 @@ impl PlayState for SessionState {
|
||||
global_state,
|
||||
&debug_info,
|
||||
&self.scene.camera(),
|
||||
global_state.clock.get_last_delta(),
|
||||
global_state.clock.dt(),
|
||||
HudInfo {
|
||||
is_aiming,
|
||||
is_first_person: matches!(
|
||||
|
@ -152,7 +152,7 @@ fn run_server(mut server: Server, rec: Receiver<Msg>, paused: Arc<AtomicBool>) {
|
||||
info!("Starting server-cli...");
|
||||
|
||||
// Set up an fps clock
|
||||
let mut clock = Clock::start();
|
||||
let mut clock = Clock::new(Duration::from_millis(1000 / TPS));
|
||||
|
||||
loop {
|
||||
// Check any event such as stopping and pausing
|
||||
@ -167,7 +167,7 @@ fn run_server(mut server: Server, rec: Receiver<Msg>, paused: Arc<AtomicBool>) {
|
||||
}
|
||||
|
||||
// Wait for the next tick.
|
||||
clock.tick(Duration::from_millis(1000 / TPS));
|
||||
clock.tick();
|
||||
|
||||
// Skip updating the server if it's paused
|
||||
if paused.load(Ordering::SeqCst) && server.number_of_players() < 2 {
|
||||
@ -177,7 +177,7 @@ fn run_server(mut server: Server, rec: Receiver<Msg>, paused: Arc<AtomicBool>) {
|
||||
}
|
||||
|
||||
let events = server
|
||||
.tick(Input::default(), clock.get_last_delta())
|
||||
.tick(Input::default(), clock.dt())
|
||||
.expect("Failed to tick server!");
|
||||
|
||||
for event in events {
|
||||
|
Loading…
Reference in New Issue
Block a user