Crate rosu_pp

Source
Expand description

Library to calculate difficulty and performance attributes for all osu! gamemodes.

A large part of rosu-pp is a port of osu!lazer’s difficulty and performance calculation with emphasis on a precise translation to Rust for the most accurate results while also providing a significant boost in performance.

Last commits of the ported code:

  • osu!lazer : 8bd65d9938a10fc42e6409501b0282f0fa4a25ef (2024-11-08)
  • osu!tools : 89b8f3b1c2e4e5674004eac4723120e7d3aef997 (2024-11-03)

News posts of the latest updates: https://osu.ppy.sh/home/news/2024-10-28-performance-points-star-rating-updates

§Usage

// Decode the map
let map = rosu_pp::Beatmap::from_path("./resources/2785319.osu").unwrap();

// Calculate difficulty attributes
let diff_attrs = rosu_pp::Difficulty::new()
    .mods(8 + 16) // HDHR
    .calculate(&map);

let stars = diff_attrs.stars();

// Calculate performance attributes
let perf_attrs = rosu_pp::Performance::new(diff_attrs)
    // To speed up the calculation, we used the previous attributes.
    // **Note** that this should only be done if the map and all difficulty
    // settings stay the same, otherwise the final attributes will be incorrect!
    .mods(24) // HDHR, must be the same as before
    .combo(789)
    .accuracy(99.2)
    .misses(2)
    .calculate();

let pp = perf_attrs.pp();

// Again, we re-use the previous attributes for maximum efficiency.
let max_pp = perf_attrs.performance()
    .mods(24) // Still the same
    .calculate()
    .pp();

println!("Stars: {stars} | PP: {pp}/{max_pp}");

§Gradual calculation

Gradually calculating attributes provides an efficient way to process each hitobject separately and calculate the attributes only up to that point.

For difficulty attributes, there is GradualDifficulty which implements Iterator and for performance attributes there is GradualPerformance which requires the current score state.

use rosu_pp::{Beatmap, GradualPerformance, Difficulty, any::ScoreState};

let map = Beatmap::from_path("./resources/1028484.osu").unwrap();

let mut gradual = Difficulty::new()
    .mods(16 + 64) // HRDT
    .clock_rate(1.2)
    .gradual_performance(&map);

let mut state = ScoreState::new(); // empty state, everything is on 0.

// The first 10 hitresults are 300s
for _ in 0..10 {
    state.n300 += 1;
    state.max_combo += 1;
    let attrs = gradual.next(state.clone()).unwrap();
    println!("PP: {}", attrs.pp());
}

// Fast-forward to the end
state.max_combo = ...
state.n300 = ...
state.n_katu = ...
...
let attrs = gradual.last(state).unwrap();
println!("PP: {}", attrs.pp());

§Accuracy

rosu-pp was tested against millions of real scores and delivered values that matched osu!lazer perfectly down to the last decimal place.

However, there is one small caveat: the values are only this precise on debug mode. On release mode, Rust’s compiler performs optimizations that produce the tiniest discrepancies due to floating point inaccuracies. With this in mind, rosu-pp is still as accurate as can be without targeting the .NET compiler itself. Realistically, the inaccuracies in release mode are negligibly small.

§Speed

An important factor for rosu-pp is the calculation speed. Optimizations and an accurate translation unfortunately don’t always go hand-in-hand. Nonetheless, performance improvements are still snuck in wherever possible, providing a significantly faster runtime than the native C# code.

Results of a rudimentary benchmark of osu!lazer and rosu-pp:

Decoding maps:            Median: 325.18ms | Mean: 325.50ms
Calculating difficulties: Median: 568.63ms | Mean: 575.97ms
Calculating performances: Median: 256.00µs | Mean: 240.40µs

rosu-pp:
Decoding maps:            Median: 46.03ms | Mean: 47.13ms
Calculating difficulties: Median: 82.11ms | Mean: 84.27ms
Calculating performances: Median: 40.57µs | Mean: 43.41µs

§Features

FlagDescriptionDependencies
defaultNo features enabled
raw_strainsWith this feature, internal strain values will be stored in a plain Vec. This introduces an out-of-memory risk on maliciously long maps (see /b/3739922), but comes with a ~5% gain in performance.
syncSome gradual calculation types can only be shared across threads if this feature is enabled. This feature adds a small performance penalty.
tracingAny error encountered during beatmap decoding will be logged through tracing::error. If this feature is not enabled, errors will be ignored.tracing

§Bindings

Using rosu-pp from other languages than Rust:

Modules§

  • Types for calculations of any mode.
  • Types for osu!catch calculations.
  • Types for osu!mania calculations.
  • Types used in and around this crate.
  • Types for osu!standard calculations.
  • Types for osu!taiko calculations.

Structs§

  • All beatmap data that is relevant for difficulty and performance calculation.
  • Difficulty calculator on maps of any mode.
  • Collection of game mods.

Enums§