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;