[![crates.io](https://img.shields.io/crates/v/rosu-pp.svg)](https://crates.io/crates/rosu-pp) [![docs](https://docs.rs/rosu-pp/badge.svg)](https://docs.rs/rosu-pp) # rosu-pp 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] : `8bd65d9938a10fc42e6409501b0282f0fa4a25ef` (2024-11-08) - [osu!tools] : `89b8f3b1c2e4e5674004eac4723120e7d3aef997` (2024-11-03) News posts of the latest updates: ### Usage ```rust // 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. ```rust 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: ```txt 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 | Flag | Description | Dependencies | ------------- | ------------------- | ------------ | `default` | No features enabled | | `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. | | `sync` | Some gradual calculation types can only be shared across threads if this feature is enabled. This feature adds a small performance penalty. | | `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