Skip to content

Commit

Permalink
Use thread::yield_now() in spin loop
Browse files Browse the repository at this point in the history
Fix FromF64Seconds conversion use floor not round
Add Default impl for SpinSleeper
  • Loading branch information
alexheretic committed Mar 12, 2018
1 parent e774325 commit 1620307
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 22 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "spin_sleep"
version = "0.3.4"
version = "0.3.5"
authors = ["Alex Butler <[email protected]>"]
description = "Accurate sleeping. Only use native sleep as far as it can be trusted, then spin."
repository = "https://github.com/alexheretic/spin-sleep"
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ spin_sleeper.sleep_s(1.01255);
spin_sleeper.sleep_ns(1_012_550_000);
```

OS-specific default accuracy settings should be good enough for most cases.
```rust
let sleeper = SpinSleeper::default();
```

### LoopHelper
For controlling & report rates (e.g. game FPS) this crate provides `LoopHelper`. A `SpinSleeper` is used to maximise
sleeping accuracy.
Expand Down Expand Up @@ -64,4 +69,4 @@ loop {

### Windows Accuracy
Windows has particularly poor accuracy by default (~15ms), `spin_sleep` will automatically
select the best accuracy on windows generally achieving ~1ms accuracy *(Since 0.3.3)*.
select the best accuracy on windows generally achieving ~1ms native sleep accuracy *(Since 0.3.3)*.
25 changes: 25 additions & 0 deletions release
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env bash

set -eu
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$dir"

tagname=$(grep -m1 version Cargo.toml | cut -d '"' -f2)

if git rev-parse "$tagname" >/dev/null 2>&1
then
echo "tag $tagname already exists" >&2
exit 1
fi

echo "Release $tagname"
read -p "continue? [y/N] " -n 1 -r
echo
if ! [[ $REPLY =~ ^[^Nn]$ ]]; then
exit 0
fi

git tag -s "$tagname" -m "Release $tagname"
git push --tags

gio open "https://github.com/alexheretic/spin-sleep/releases/new?tag=$tagname"
33 changes: 27 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@
//! spin_sleeper.sleep_s(1.01255);
//! spin_sleeper.sleep_ns(1_012_550_000);
//! ```
//!
//! OS-specific default accuracy settings should be good enough for most cases.
//! ```
//! # use spin_sleep::SpinSleeper;
//! let sleeper = SpinSleeper::default();
//! # let _ = sleeper;
//! ```
#[cfg(test)]
#[macro_use]
extern crate approx;
Expand Down Expand Up @@ -62,6 +69,9 @@ pub struct SpinSleeper {
native_accuracy_ns: u32,
}

#[cfg(not(windows))]
const DEFAULT_NATIVE_SLEEP_ACCURACY: SubsecondNanoseconds = 125_000;

#[cfg(not(windows))]
#[inline]
pub(crate) fn thread_sleep(duration: Duration) {
Expand Down Expand Up @@ -101,6 +111,18 @@ pub(crate) fn thread_sleep(duration: Duration) {
}
}

impl Default for SpinSleeper {
/// Constructs new SpinSleeper with defaults suiting the current OS
fn default() -> Self {
#[cfg(windows)]
let accuracy = *MIN_TIME_PERIOD * 1_000_000;
#[cfg(not(windows))]
let accuracy = DEFAULT_NATIVE_SLEEP_ACCURACY;

SpinSleeper::new(accuracy)
}
}

impl SpinSleeper {
/// Constructs new SpinSleeper with the input native sleep accuracy.
/// The lower the `native_accuracy_ns` the more we effectively trust the accuracy of the
Expand All @@ -120,20 +142,19 @@ impl SpinSleeper {
let start = Instant::now();
let accuracy = Duration::new(0, self.native_accuracy_ns);
if duration > accuracy {
thread_sleep(duration - accuracy)
thread_sleep(duration - accuracy);
}
// spin the rest of the duration
while start.elapsed() < duration {}
while start.elapsed() < duration {
thread::yield_now();
}
}

/// Puts the current thread to sleep and then/or spins until the specified
/// float second duration has elapsed.
pub fn sleep_s(&self, seconds: Seconds) {
if seconds > 0.0 {
self.sleep(Duration::new(
seconds.floor() as u64,
((seconds % 1f64) * 1_000_000_000f64).round() as u32,
))
self.sleep(Duration::from_f64_secs(seconds));
}
}

Expand Down
18 changes: 4 additions & 14 deletions src/loop_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,11 @@ use super::*;
use std::time::{Duration, Instant};
use std::f64;

#[cfg(not(windows))]
const DEFAULT_NATIVE_SLEEP_ACCURACY: SubsecondNanoseconds = 125_000;

trait ToF64Seconds {
pub(crate) trait ToF64Seconds {
fn to_f64_secs(&self) -> Seconds;
}

trait FromF64Seconds<T> {
pub(crate) trait FromF64Seconds<T> {
fn from_f64_secs(seconds: Seconds) -> T;
}

Expand All @@ -23,7 +20,7 @@ impl ToF64Seconds for Duration {

impl FromF64Seconds<Duration> for Duration {
fn from_f64_secs(seconds: Seconds) -> Duration {
let whole_seconds = seconds.round() as u64;
let whole_seconds = seconds.floor() as u64;
let subsec_nanos = (seconds.fract() * 1_000_000_000_f64).round() as u32;
Duration::new(whole_seconds, subsec_nanos)
}
Expand Down Expand Up @@ -126,14 +123,7 @@ impl LoopHelperBuilder {
LoopHelper {
target_delta: Duration::from_f64_secs(1.0 / target_rate),
report_interval: interval,
sleeper: self.sleeper.unwrap_or_else(|| {
#[cfg(windows)]
let accuracy = *MIN_TIME_PERIOD * 1_000_000;
#[cfg(not(windows))]
let accuracy = DEFAULT_NATIVE_SLEEP_ACCURACY;

SpinSleeper::new(accuracy)
}),
sleeper: self.sleeper.unwrap_or_else(SpinSleeper::default),
last_report: now - interval,
last_loop_start: now,
delta_sum: Duration::from_secs(0),
Expand Down

0 comments on commit 1620307

Please sign in to comment.