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