Skip to main content

rosu_pp/
lib.rs

1//! Library to calculate difficulty and performance attributes for all [osu!] gamemodes.
2//!
3//! A large part of `rosu-pp` is a port of [osu!lazer]'s difficulty and performance calculation
4//! with emphasis on a precise translation to Rust for the most [accurate results](#accuracy)
5//! while also providing a significant [boost in performance](#speed).
6//!
7//! Last commits of the ported code:
8//!   - [osu!lazer] : `28c846b4d9366484792e27f4729cd1afa2cdeb66` (2025-10-13)
9//!   - [osu!tools] : `ab97b64f60901952926b2121ddffb8976d7f8775` (2025-10-16)
10//!
11//! News posts of the latest updates: <https://osu.ppy.sh/home/news/2025-10-29-performance-points-star-rating-updates>
12//!
13//! ## Usage
14//!
15//! ```
16//! // Decode the map
17//! let map = rosu_pp::Beatmap::from_path("./resources/2785319.osu").unwrap();
18//!
19//! // Whereas osu! simply times out on malicious maps, rosu-pp does not. To
20//! // prevent potential performance/memory issues, it is recommended to check
21//! // beforehand whether a map is too suspicious for further calculation.
22//! // Alternatively, using e.g. `checked_calculate` instead of `calculate`
23//! // performs the same suspicion check before calculating.
24//! if let Err(sus) = map.check_suspicion() {
25//!     panic!("{sus:?}");
26//! }
27//!
28//! // Calculate difficulty attributes
29//! let diff_attrs = rosu_pp::Difficulty::new()
30//!     .mods(8 + 16) // HDHR
31//!     .calculate(&map); // or `checked_calculate`
32//!
33//! let stars = diff_attrs.stars();
34//!
35//! // Calculate performance attributes
36//! let perf_attrs = rosu_pp::Performance::new(diff_attrs)
37//!     // To speed up the calculation, we used the previous attributes.
38//!     // **Note** that this should only be done if the map and all difficulty
39//!     // settings stay the same, otherwise the final attributes will be incorrect!
40//!     .mods(24) // HDHR, must be the same as before
41//!     .combo(789)
42//!     .accuracy(99.2)
43//!     .misses(2)
44//!     .calculate();
45//!
46//! let pp = perf_attrs.pp();
47//!
48//! // Again, we re-use the previous attributes for maximum efficiency.
49//! let max_pp = perf_attrs.performance()
50//!     .mods(24) // Still the same
51//!     .calculate()
52//!     .pp();
53//!
54//! println!("Stars: {stars} | PP: {pp}/{max_pp}");
55//! ```
56//!
57//! ## Gradual calculation
58//!
59//! Gradually calculating attributes provides an efficient way to process each hitobject
60//! separately and calculate the attributes only up to that point.
61//!
62//! For difficulty attributes, there is [`GradualDifficulty`] which implements `Iterator`
63//! and for performance attributes there is [`GradualPerformance`] which requires the current
64//! score state.
65//!
66//! ```
67//! use rosu_pp::{Beatmap, GradualPerformance, Difficulty, any::ScoreState};
68//!
69//! let map = Beatmap::from_path("./resources/1028484.osu").unwrap();
70//!
71//! let mut gradual = Difficulty::new()
72//!     .mods(16 + 64) // HRDT
73//!     .clock_rate(1.2)
74//!     .gradual_performance(&map);
75//!
76//! let mut state = ScoreState::new(); // empty state, everything is on 0.
77//!
78//! // The first 10 hitresults are 300s
79//! for _ in 0..10 {
80//!     state.n300 += 1;
81//!     state.max_combo += 1;
82//!     let attrs = gradual.next(state.clone()).unwrap();
83//!     println!("PP: {}", attrs.pp());
84//! }
85//!
86//! // Fast-forward to the end
87//! # /*
88//! state.max_combo = ...
89//! state.n300 = ...
90//! state.n_katu = ...
91//! ...
92//! # */
93//! let attrs = gradual.last(state).unwrap();
94//! println!("PP: {}", attrs.pp());
95//! ```
96//!
97//! ## Accuracy
98//!
99//! `rosu-pp` was tested against millions of real scores and delivered
100//! values that matched osu!lazer perfectly down to the last decimal place.
101//!
102//! However, there is one small caveat: the values are only this precise on debug mode.
103//! On release mode, Rust's compiler performs optimizations that produce the tiniest discrepancies
104//! due to floating point inaccuracies. With this in mind, `rosu-pp` is still as accurate as can
105//! be without targeting the .NET compiler itself.
106//! Realistically, the inaccuracies in release mode are negligibly small.
107//!
108//! ## Speed
109//!
110//! An important factor for `rosu-pp` is the calculation speed. Optimizations and an accurate translation
111//! unfortunately don't always go hand-in-hand. Nonetheless, performance improvements are still
112//! snuck in wherever possible, providing a significantly faster runtime than the native C# code.
113//!
114//! Results of a rudimentary [benchmark] of osu!lazer and rosu-pp:
115//! ```txt
116//! osu!lazer:
117//! Decoding maps:            Median: 325.18ms | Mean: 325.50ms
118//! Calculating difficulties: Median: 568.63ms | Mean: 575.97ms
119//! Calculating performances: Median: 256.00µs | Mean: 240.40µs
120//!
121//! rosu-pp:
122//! Decoding maps:            Median: 46.03ms | Mean: 47.13ms
123//! Calculating difficulties: Median: 82.11ms | Mean: 84.27ms
124//! Calculating performances: Median: 40.57µs | Mean: 43.41µs
125//! ```
126//!
127//! ## Features
128//!
129//! | Flag          | Description         | Dependencies
130//! | ------------- | ------------------- | ------------
131//! | `default`     | No features enabled |
132//! | `sync`        | Some gradual calculation types can only be shared across threads if this feature is enabled. This feature adds a small performance penalty. |
133//! | `tracing`     | Any error encountered during beatmap decoding will be logged through `tracing::error`. If this feature is **not** enabled, errors will be ignored. | [`tracing`]
134//!
135//! ## Bindings
136//!
137//! Using `rosu-pp` from other languages than Rust:
138//! - JavaScript: [rosu-pp-js]
139//! - Python: [rosu-pp-py]
140//!
141//! [osu!]: https://osu.ppy.sh/home
142//! [osu!lazer]: https://github.com/ppy/osu
143//! [osu!tools]: https://github.com/ppy/osu-tools
144//! [`tracing`]: https://docs.rs/tracing
145//! [rosu-pp-js]: https://github.com/MaxOhn/rosu-pp-js
146//! [rosu-pp-py]: https://github.com/MaxOhn/rosu-pp-py
147//! [benchmark]: https://gist.github.com/MaxOhn/625af10011f6d7e13a171b08ccf959ff
148//! [`GradualDifficulty`]: crate::any::GradualDifficulty
149//! [`GradualPerformance`]: crate::any::GradualPerformance
150
151#![deny(rustdoc::broken_intra_doc_links, rustdoc::missing_crate_level_docs)]
152#![warn(clippy::missing_const_for_fn, clippy::pedantic)]
153#![allow(
154    clippy::missing_errors_doc,
155    clippy::module_name_repetitions,
156    clippy::must_use_candidate,
157    clippy::struct_excessive_bools,
158    clippy::match_same_arms,
159    clippy::cast_possible_truncation,
160    clippy::cast_precision_loss,
161    clippy::cast_sign_loss,
162    clippy::explicit_iter_loop,
163    clippy::similar_names,
164    clippy::cast_possible_wrap,
165    clippy::manual_midpoint
166)]
167
168#[doc(inline)]
169pub use self::{
170    any::{Difficulty, GradualDifficulty, GradualPerformance, Performance},
171    model::{beatmap::Beatmap, mods::GameMods},
172};
173
174#[macro_use]
175mod util;
176
177/// Types for calculations of any mode.
178pub mod any;
179
180/// Types for osu!standard calculations.
181pub mod osu;
182
183/// Types for osu!taiko calculations.
184pub mod taiko;
185
186/// Types for osu!catch calculations.
187pub mod catch;
188
189/// Types for osu!mania calculations.
190pub mod mania;
191
192/// Types used in and around this crate.
193pub mod model;