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 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
//! 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](#accuracy)
//! while also providing a significant [boost in performance](#speed).
//!
//! Last commits of the ported code:
//! - [osu!lazer] : `7342fb7f51b34533a42bffda89c3d6c569cc69ce` (2022-10-11)
//! - [osu!tools] : `146d5916937161ef65906aa97f85d367035f3712` (2022-10-08)
//!
//! News posts of the latest gamemode updates:
//! - osu: <https://osu.ppy.sh/home/news/2022-09-30-changes-to-osu-sr-and-pp>
//! - taiko: <https://osu.ppy.sh/home/news/2022-09-28-changes-to-osu-taiko-sr-and-pp>
//! - catch: <https://osu.ppy.sh/home/news/2020-05-14-osucatch-scoring-updates>
//! - mania: <https://osu.ppy.sh/home/news/2022-10-09-changes-to-osu-mania-sr-and-pp>
//!
//! ## 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 all current beatmaps on multiple mod combinations 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 which can cascade into larger differences in the end.
//! 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:
//! ```txt
//! osu!lazer:
//! Decoding maps: Median: 378.10ms | Mean: 381.47ms
//! Calculating difficulties: Median: 588.89ms | Mean: 597.11ms
//! Calculating performances: Median: 315.90µs | Mean: 310.60µs
//!
//! rosu-pp:
//! Decoding maps: Median: 46.94ms | Mean: 47.21ms
//! Calculating difficulties: Median: 72.90ms | Mean: 73.13ms
//! Calculating performances: Median: 44.13µs | Mean: 45.53µs
//! ```
//!
//! ## Features
//!
//! | Flag | Description | Dependencies
//! | ----------------- | ------------------------------------- | ------------
//! | `default` | Enables the `compact_strains` feature |
//! | `compact_strains` | Storing internal strain values in a plain Vec introduces an out-of-memory risk on maliciously long maps (see [/b/3739922](https://osu.ppy.sh/b/3739922)). This feature stores strains more compactly, but comes with a ~5% loss in performance. |
//! | `sync` | Some gradual calculation types can only be shared across threads if this feature is enabled. This adds a performance penalty so only enable this if really needed. |
//! | `tracing` | Any 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:
//! - JavaScript: [rosu-pp-js]
//! - Python: [rosu-pp-py]
//!
//! [osu!]: https://osu.ppy.sh/home
//! [osu!lazer]: https://github.com/ppy/osu
//! [osu!tools]: https://github.com/ppy/osu-tools
//! [`tracing`]: https://docs.rs/tracing
//! [rosu-pp-js]: https://github.com/MaxOhn/rosu-pp-js
//! [rosu-pp-py]: https://github.com/MaxOhn/rosu-pp-py
//! [benchmark]: https://gist.github.com/MaxOhn/625af10011f6d7e13a171b08ccf959ff
//! [`GradualDifficulty`]: crate::any::GradualDifficulty
//! [`GradualPerformance`]: crate::any::GradualPerformance
#![deny(rustdoc::broken_intra_doc_links, rustdoc::missing_crate_level_docs)]
#![warn(clippy::missing_const_for_fn, clippy::pedantic)]
#![allow(
clippy::missing_errors_doc,
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::struct_excessive_bools,
clippy::match_same_arms,
clippy::cast_possible_truncation,
clippy::cast_precision_loss,
clippy::cast_sign_loss,
clippy::explicit_iter_loop,
clippy::similar_names,
clippy::cast_possible_wrap
)]
#[doc(inline)]
pub use self::{
any::{Difficulty, GradualDifficulty, GradualPerformance, Performance},
model::beatmap::{Beatmap, Converted},
};
/// Types for calculations of any mode.
pub mod any;
/// Types for osu!standard calculations.
pub mod osu;
/// Types for osu!taiko calculations.
pub mod taiko;
/// Types for osu!catch calculations.
pub mod catch;
/// Types for osu!mania calculations.
pub mod mania;
/// Types used in and around this crate.
pub mod model;
mod util;