feat: use rosu-mods (#9)

* use rosu-mods

* mention mods in readme

* use published rosu-pp

* remove test remnants
This commit is contained in:
Badewanne3
2024-07-12 13:42:24 +02:00
committed by GitHub
parent 4d29dd752b
commit bf6e57a520
10 changed files with 890 additions and 30 deletions
+2
View File
@@ -1,4 +1,6 @@
/target
expanded.rs
output.rs
# Byte-compiled / optimized / DLL files
__pycache__/
Generated
+35 -3
View File
@@ -141,12 +141,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c55926c8f0fed1db12fbe96f7a6083a2c4186443dd32532ab34e6902467a4f3"
[[package]]
name = "rosu-pp"
version = "1.0.0"
name = "rosu-mods"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26f146c66bed5900ee1fa2b55ef5cc5dd2dbd45e6cac0f7bee5cae535980afbc"
checksum = "d69daf02885f7477085403a6eada6215f44333c7b54355ea1c4e276a02263bde"
dependencies = [
"serde",
]
[[package]]
name = "rosu-pp"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "002a6b12cedcb185f4051f0b3d0466e0b61ff414a9ca8375f09be581c0e70f06"
dependencies = [
"rosu-map",
"rosu-mods",
]
[[package]]
@@ -154,7 +164,29 @@ name = "rosu-pp-py"
version = "1.0.1"
dependencies = [
"pyo3",
"rosu-mods",
"rosu-pp",
"serde",
]
[[package]]
name = "serde"
version = "1.0.203"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.203"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
+2
View File
@@ -14,7 +14,9 @@ crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.22", features = ["extension-module", "macros"] }
rosu-mods = { version = "0.1.0", default-features = false, features = ["serde"] }
rosu-pp = { version = "1.0.0", features = ["sync"] }
serde = "1.0.203"
[profile.release]
lto = true
+38
View File
@@ -118,6 +118,44 @@ while True:
i += 1
```
## Mods
Wherever mods are specified, their type should coincide with the following alias definition:
```py
GameMods = Union[int, str, GameMod, List[Union[GameMod, str, int]]]
GameMod = dict[str, Union[str, GameModSettings]]
GameModSettings = dict[str, Union[bool, float, str]]
```
That means, mods can be given either through their [(legacy) bitflags](https://github.com/ppy/osu-api/wiki#reference),
a string for acronyms, a "GameMod" `dict`, or a sequence whose items are either
a "GameMod" `dict`, a single acronym string, or bitflags for a single mod.
A "GameMod" `dict` **must** have the item `'acronym': str` and an optional item `'settings': GameModSettings`.
Some examples for valid mods look as follows:
```py
mods = 8 + 64 # Hidden, DoubleTime
mods = "hRNcWIez" # HardRock, Nightcore, Wiggle, Easy
mods = { 'acronym': "FI" } # FadeIn
mods = [
1024,
'nf',
{
'acronym': "AC",
'settings': {
'minimum_accuracy': 95,
'restart': True
}
}
] # Flashlight, NoFail, AccuracyChallenge
import json
mods_json = '[{"acronym": "TC"}, {"acronym": "HT", "settings": {"speed_change": 0.6}}]'
mods = json.loads(mods_json) # Traceable, HalfTime
```
## Installing rosu-pp-py
Installing rosu-pp-py requires a [supported version of Python and Rust](https://github.com/PyO3/PyO3#usage).
+43 -9
View File
@@ -2,6 +2,13 @@ from enum import Enum
from typing import List, Optional, Union
from collections.abc import Iterator
GameMods = Union[int, str, GameMod, List[Union[GameMod, str, int]]]
GameMod = dict[str, Union[str, GameModSettings]]
"""
Must contain item `'acronym': str` and optionally `'settings': GameModSettings`
"""
GameModSettings = dict[str, Union[bool, float, str]]
class GameMode(Enum):
"""
Enum for a beatmap's gamemode
@@ -105,8 +112,17 @@ class Difficulty:
Builder for a difficulty calculation
The kwargs may include any of the following:
`'mods': int`
Specify mods through their bit values.
`'mods': GameMods`
Specify mods.
Relevant type aliases:
`GameMods = Union[int, str, GameMod, List[Union[GameMod, str, int]]]`
`GameMod = dict[str, Union[str, GameModSettings]]`
`GameMod` *must* have an item `'acronym': str` and an optional
item `'settings': GameModSettings`
`GameModSettings = dict[str, Union[bool, float, str]]`
See https://github.com/ppy/osu-api/wiki#mods
`'clock_rate': float`
@@ -194,7 +210,7 @@ class Difficulty:
Returns a gradual performance calculator for the current difficulty settings
"""
def set_mods(self, mods: Optional[int]) -> None: ...
def set_mods(self, mods: Optional[GameMods]) -> None: ...
def set_clock_rate(self, clock_rate: Optional[float]) -> None: ...
@@ -255,8 +271,17 @@ class Performance:
Builder for a performance calculation
The kwargs may include any of the following:
`'mods': int`
Specify mods through their bit values.
`'mods': GameMods`
Specify mods.
Relevant type aliases:
`GameMods = Union[int, str, GameMod, List[Union[GameMod, str, int]]]`
`GameMod = dict[str, Union[str, GameModSettings]]`
`GameMod` *must* have an item `'acronym': str` and an optional
item `'settings': GameModSettings`
`GameModSettings = dict[str, Union[bool, float, str]]`
See https://github.com/ppy/osu-api/wiki#mods
`'clock_rate': float`
@@ -364,7 +389,7 @@ class Performance:
Use the current difficulty settings to create a difficulty calculator
"""
def set_mods(self, mods: Optional[int]) -> None: ...
def set_mods(self, mods: Optional[GameMods]) -> None: ...
def set_clock_rate(self, clock_rate: Optional[float]) -> None: ...
@@ -501,8 +526,17 @@ class BeatmapAttributesBuilder:
Specify a gamemode
`'is_convert': bool`
Specify whether it's a converted map
`'mods': int`
Specify mods through their bit values.
`'mods': GameMods`
Specify mods.
Relevant type aliases:
`GameMods = Union[int, str, GameMod, List[Union[GameMod, str, int]]]`
`GameMod = dict[str, Union[str, GameModSettings]]`
`GameMod` *must* have an item `'acronym': str` and an optional
item `'settings': GameModSettings`
`GameModSettings = dict[str, Union[bool, float, str]]`
See https://github.com/ppy/osu-api/wiki#mods
`'clock_rate': float`
@@ -564,7 +598,7 @@ class BeatmapAttributesBuilder:
def set_mode(self, mode: Optional[GameMode], is_convert: bool) -> None: ...
def set_mods(self, mods: Optional[int]) -> None: ...
def set_mods(self, mods: Optional[GameMods]) -> None: ...
def set_clock_rate(self, clock_rate: Optional[float]) -> None: ...
+12 -6
View File
@@ -6,14 +6,14 @@ use pyo3::{
};
use rosu_pp::model::beatmap::{BeatmapAttributes, BeatmapAttributesBuilder, HitWindows};
use crate::{beatmap::PyBeatmap, error::ArgsError, mode::PyGameMode};
use crate::{beatmap::PyBeatmap, error::ArgsError, mode::PyGameMode, mods::PyGameMods};
#[pyclass(name = "BeatmapAttributesBuilder")]
#[derive(Default)]
pub struct PyBeatmapAttributesBuilder {
mode: Option<PyGameMode>,
is_convert: bool,
mods: u32,
mods: PyGameMods,
clock_rate: Option<f64>,
ar: Option<f32>,
ar_with_mods: bool,
@@ -59,7 +59,7 @@ impl PyBeatmapAttributesBuilder {
"mods" => {
this.mods = value
.extract()
.map_err(|_| PyTypeError::new_err("kwarg 'mods': must be an int"))?
.map_err(|_| PyTypeError::new_err("kwarg 'mods': must be GameMods"))?
}
"clock_rate" => {
this.clock_rate =
@@ -132,7 +132,13 @@ impl PyBeatmapAttributesBuilder {
}
fn build(&self) -> PyBeatmapAttributes {
let mut builder = BeatmapAttributesBuilder::new().mods(self.mods);
let mut builder = BeatmapAttributesBuilder::new();
builder = match self.mods {
PyGameMods::Lazer(ref mods) => builder.mods(mods.clone()),
PyGameMods::Intermode(ref mods) => builder.mods(mods),
PyGameMods::Legacy(mods) => builder.mods(mods),
};
if let Some(mode) = self.mode {
builder = builder.mode(mode.into(), self.is_convert);
@@ -179,8 +185,8 @@ impl PyBeatmapAttributesBuilder {
}
#[pyo3(signature = (mods=None))]
fn set_mods(&mut self, mods: Option<u32>) {
self.mods = mods.unwrap_or(0);
fn set_mods(&mut self, mods: Option<PyGameMods>) {
self.mods = mods.unwrap_or_default();
}
#[pyo3(signature = (clock_rate=None))]
+13 -6
View File
@@ -11,6 +11,7 @@ use crate::{
beatmap::PyBeatmap,
error::ArgsError,
gradual::{difficulty::PyGradualDifficulty, performance::PyGradualPerformance},
mods::PyGameMods,
performance::PyPerformance,
strains::PyStrains,
};
@@ -18,7 +19,7 @@ use crate::{
#[pyclass(name = "Difficulty")]
#[derive(Default)]
pub struct PyDifficulty {
pub(crate) mods: u32,
pub(crate) mods: PyGameMods,
pub(crate) clock_rate: Option<f64>,
pub(crate) ar: Option<f32>,
pub(crate) ar_with_mods: bool,
@@ -48,7 +49,7 @@ impl PyDifficulty {
"mods" => {
this.mods = value
.extract()
.map_err(|_| PyTypeError::new_err("kwarg 'mods': must be an int"))?
.map_err(|_| PyTypeError::new_err("kwarg 'mods': must be GameMods"))?
}
"clock_rate" => {
this.clock_rate =
@@ -155,7 +156,7 @@ impl PyDifficulty {
} = self;
PyPerformance {
mods: *mods,
mods: mods.clone(),
clock_rate: *clock_rate,
ar: *ar,
ar_with_mods: *ar_with_mods,
@@ -180,8 +181,8 @@ impl PyDifficulty {
}
#[pyo3(signature = (mods=None))]
fn set_mods(&mut self, mods: Option<u32>) {
self.mods = mods.unwrap_or(0);
fn set_mods(&mut self, mods: Option<PyGameMods>) {
self.mods = mods.unwrap_or_default();
}
#[pyo3(signature = (clock_rate=None))]
@@ -226,7 +227,13 @@ impl PyDifficulty {
impl PyDifficulty {
pub fn as_difficulty(&self) -> Difficulty {
let mut difficulty = Difficulty::new().mods(self.mods);
let mut difficulty = Difficulty::new();
difficulty = match self.mods {
PyGameMods::Lazer(ref mods) => difficulty.mods(mods.clone()),
PyGameMods::Intermode(ref mods) => difficulty.mods(mods),
PyGameMods::Legacy(mods) => difficulty.mods(mods),
};
if let Some(passed_objects) = self.passed_objects {
difficulty = difficulty.passed_objects(passed_objects);
+1
View File
@@ -28,6 +28,7 @@ mod difficulty;
mod error;
mod gradual;
mod mode;
mod mods;
mod performance;
mod score_state;
mod strains;
+731
View File
@@ -0,0 +1,731 @@
use std::{
error::Error as StdError,
fmt::{Display, Formatter, Result as FmtResult},
ops::Deref,
};
use pyo3::{
impl_::frompyobject::{
extract_struct_field, extract_tuple_struct_field, failed_to_extract_enum,
},
intern,
types::{
iter::BoundDictIterator, PyAnyMethods, PyDict, PyDictMethods, PyList, PyString,
PyStringMethods,
},
Bound, FromPyObject, PyAny, PyResult,
};
use rosu_mods::{serde::GameModSeed, GameMods as GameModsLazer, GameModsIntermode, GameModsLegacy};
use serde::de::{
value::{BorrowedStrDeserializer, CowStrDeserializer, MapAccessDeserializer, U32Deserializer},
DeserializeSeed, Deserializer, Error as DeError, MapAccess, Unexpected, Visitor,
};
use crate::error::ParseError;
#[derive(Clone)]
pub enum PyGameMods {
Lazer(GameModsLazer),
Intermode(GameModsIntermode),
Legacy(GameModsLegacy),
}
impl Default for PyGameMods {
fn default() -> Self {
Self::Legacy(GameModsLegacy::NoMod)
}
}
impl<'py> FromPyObject<'py> for PyGameMods {
fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult<Self> {
let errors = [
match extract_tuple_struct_field(obj, "PyInt", 0) {
Ok(bits) => return Ok(Self::Legacy(GameModsLegacy::from_bits(bits))),
Err(err) => err,
},
match extract_tuple_struct_field::<Bound<'py, PyString>>(obj, "PyString", 0) {
Ok(acronyms) => match acronyms.to_str() {
Ok(acronyms) => {
let intermode = GameModsIntermode::from_acronyms(acronyms);
let mods = match intermode.checked_bits() {
Some(bits) => Self::Legacy(GameModsLegacy::from_bits(bits)),
None => Self::Intermode(intermode),
};
return Ok(mods);
}
Err(err) => err,
},
Err(err) => err,
},
match extract_tuple_struct_field::<PyGameMod<'py>>(obj, "PyGameMod", 0) {
Ok(gamemod) => match GameModSeed::GuessMode.deserialize(gamemod) {
Ok(gamemod) => return Ok(Self::Lazer(gamemod.into())),
Err(DeserializeError(err)) => ParseError::new_err(err),
},
Err(err) => err,
},
match extract_tuple_struct_field::<Bound<'py, PyList>>(obj, "PyList", 0) {
Ok(list) => {
let seed = GameModSeed::GuessMode;
let res = list
.iter()?
.try_fold(GameModsLazer::new(), |mut mods, item| {
let res = match item?.extract::<PyGameModUnion<'_>>()? {
PyGameModUnion::Mod(gamemod) => seed.deserialize(gamemod),
PyGameModUnion::Acronym(acronym) => seed.deserialize(
CowStrDeserializer::new(acronym.to_string_lossy()),
),
PyGameModUnion::Bits(bits) => {
seed.deserialize(U32Deserializer::new(bits))
}
};
match res {
Ok(gamemod) => mods.insert(gamemod),
Err(DeserializeError(err)) => return Err(ParseError::new_err(err)),
}
Ok(mods)
});
match res {
Ok(mods) => return Ok(Self::Lazer(mods)),
Err(err) => err,
}
}
Err(err) => err,
},
];
Err(failed_to_extract_enum(
obj.py(),
"PyGameMods",
&["Legacy", "Intermode", "GameMods", "GameMods"],
&["int", "str", "GameMod", "List[GameMod | str | int]"],
&errors,
))
}
}
struct PyGameMod<'py> {
acronym: Bound<'py, PyString>,
settings: Option<Bound<'py, PyDict>>,
}
impl<'py> FromPyObject<'py> for PyGameMod<'py> {
fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult<Self> {
let py = obj.py();
let dict: Bound<'_, PyDict> = obj.extract()?;
// Force a `KeyError` if `acronym` is missing
let acronym = PyAnyMethods::get_item(dict.deref(), intern!(py, "acronym"))?;
let settings = dict.get_item(intern!(py, "settings"))?;
Ok(PyGameMod {
acronym: extract_struct_field(&acronym, "PyGameMod", "acronym")?,
settings: settings.as_ref().map(PyAnyMethods::extract).transpose()?,
})
}
}
#[derive(FromPyObject)]
enum PyGameModUnion<'py> {
Mod(PyGameMod<'py>),
Acronym(Bound<'py, PyString>),
Bits(u32),
}
#[derive(Debug)]
struct DeserializeError(String);
impl DeError for DeserializeError {
fn custom<T: Display>(msg: T) -> Self {
Self(msg.to_string())
}
}
impl Display for DeserializeError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.write_str(&self.0)
}
}
impl StdError for DeserializeError {}
impl<'de> Deserializer<'de> for PyGameMod<'de> {
type Error = DeserializeError;
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
self.deserialize_map(visitor)
}
fn deserialize_bool<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_i8<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_i16<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_i32<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_i64<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_u8<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_u16<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_u32<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_u64<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_f32<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_f64<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_char<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_str<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_string<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_bytes<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_byte_buf<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_option<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_unit<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_unit_struct<V>(self, _: &'static str, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_newtype_struct<V>(self, _: &'static str, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_seq<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_tuple<V>(self, _: usize, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_tuple_struct<V>(
self,
_: &'static str,
_: usize,
_: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_map(PyGameModMap::Full(self))
}
fn deserialize_struct<V>(
self,
_: &'static str,
_: &'static [&'static str],
_: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_enum<V>(
self,
_: &'static str,
_: &'static [&'static str],
_: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_identifier<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_ignored_any<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
}
enum PyGameModMap<'py> {
Full(PyGameMod<'py>),
Settings(Bound<'py, PyDict>),
Done,
}
impl<'de> MapAccess<'de> for PyGameModMap<'de> {
type Error = DeserializeError;
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
where
K: DeserializeSeed<'de>,
{
let key = match self {
PyGameModMap::Full(_) => "acronym",
PyGameModMap::Settings(_) => "settings",
PyGameModMap::Done => return Ok(None),
};
seed.deserialize(BorrowedStrDeserializer::new(key))
.map(Some)
}
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>
where
V: DeserializeSeed<'de>,
{
match self {
PyGameModMap::Full(gamemod) => {
let acronym = gamemod.acronym.to_string_lossy();
let res = seed.deserialize(CowStrDeserializer::new(acronym));
*self = gamemod.settings.take().map_or(Self::Done, Self::Settings);
res
}
PyGameModMap::Settings(dict) => {
let access = DictAccess {
iter: dict.iter(),
next_value: None,
};
let res = seed.deserialize(MapAccessDeserializer::new(access));
*self = Self::Done;
res
}
PyGameModMap::Done => unimplemented!(),
}
}
}
struct DictAccess<'py> {
iter: BoundDictIterator<'py>,
next_value: Option<PyValue<'py>>,
}
impl<'de> MapAccess<'de> for DictAccess<'de> {
type Error = DeserializeError;
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
where
K: DeserializeSeed<'de>,
{
debug_assert!(self.next_value.is_none());
match self.iter.next() {
Some((key, value)) => {
let key: Bound<'_, PyString> = key.extract().map_err(DeError::custom)?;
let value: PyValue<'_> = value.extract().map_err(DeError::custom)?;
self.next_value = Some(value);
seed.deserialize(PyValue::String(key)).map(Some)
}
None => Ok(None),
}
}
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>
where
V: DeserializeSeed<'de>,
{
seed.deserialize(self.next_value.take().unwrap())
}
fn size_hint(&self) -> Option<usize> {
Some(self.iter.len())
}
}
#[derive(FromPyObject)]
enum PyValue<'py> {
Bool(bool),
Number(f32),
String(Bound<'py, PyString>),
}
impl<'de> Deserializer<'de> for PyValue<'de> {
type Error = DeserializeError;
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
match self {
PyValue::Bool(v) => visitor.visit_bool(v),
PyValue::Number(v) => visitor.visit_f32(v),
PyValue::String(v) => visitor.visit_string(v.to_string_lossy().into_owned()),
}
}
fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
match self {
Self::Bool(v) => visitor.visit_bool(v),
Self::Number(v) => Err(DeError::invalid_type(
Unexpected::Float(f64::from(v)),
&visitor,
)),
Self::String(v) => Err(DeError::invalid_type(
Unexpected::Str(v.to_string_lossy().as_ref()),
&visitor,
)),
}
}
fn deserialize_i8<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_i16<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_i32<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_i64<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_u8<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_u16<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_u32<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_u64<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_f32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
match self {
Self::Bool(v) => Err(DeError::invalid_type(Unexpected::Bool(v), &visitor)),
Self::Number(v) => visitor.visit_f32(v),
Self::String(v) => Err(DeError::invalid_type(
Unexpected::Str(v.to_string_lossy().as_ref()),
&visitor,
)),
}
}
fn deserialize_f64<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_char<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
match self {
Self::Bool(v) => Err(DeError::invalid_type(Unexpected::Bool(v), &visitor)),
Self::Number(v) => Err(DeError::invalid_type(
Unexpected::Float(f64::from(v)),
&visitor,
)),
Self::String(v) => visitor.visit_str(v.to_string_lossy().as_ref()),
}
}
fn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
match self {
Self::Bool(v) => Err(DeError::invalid_type(Unexpected::Bool(v), &visitor)),
Self::Number(v) => Err(DeError::invalid_type(
Unexpected::Float(f64::from(v)),
&visitor,
)),
Self::String(v) => visitor.visit_string(v.to_string_lossy().into_owned()),
}
}
fn deserialize_bytes<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_byte_buf<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_some(self)
}
fn deserialize_unit<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_unit_struct<V>(self, _: &'static str, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_newtype_struct<V>(self, _: &'static str, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_seq<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_tuple<V>(self, _: usize, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_tuple_struct<V>(
self,
_: &'static str,
_: usize,
_: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_map<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_struct<V>(
self,
_: &'static str,
_: &'static [&'static str],
_: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_enum<V>(
self,
_: &'static str,
_: &'static [&'static str],
_: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_identifier<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
unimplemented!()
}
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_unit()
}
}
+13 -6
View File
@@ -14,12 +14,13 @@ use crate::{
beatmap::PyBeatmap,
difficulty::PyDifficulty,
error::ArgsError,
mods::PyGameMods,
};
#[pyclass(name = "Performance")]
#[derive(Default)]
pub struct PyPerformance {
pub(crate) mods: u32,
pub(crate) mods: PyGameMods,
pub(crate) clock_rate: Option<f64>,
pub(crate) ar: Option<f32>,
pub(crate) ar_with_mods: bool,
@@ -58,7 +59,7 @@ impl PyPerformance {
"mods" => {
this.mods = value
.extract()
.map_err(|_| PyTypeError::new_err("kwarg 'mods': must be an int"))?
.map_err(|_| PyTypeError::new_err("kwarg 'mods': must be GameMods"))?
}
"clock_rate" => {
this.clock_rate =
@@ -247,7 +248,7 @@ impl PyPerformance {
} = self;
PyDifficulty {
mods: *mods,
mods: mods.clone(),
clock_rate: *clock_rate,
ar: *ar,
ar_with_mods: *ar_with_mods,
@@ -263,8 +264,8 @@ impl PyPerformance {
}
#[pyo3(signature = (mods=None))]
fn set_mods(&mut self, mods: Option<u32>) {
self.mods = mods.unwrap_or(0);
fn set_mods(&mut self, mods: Option<PyGameMods>) {
self.mods = mods.unwrap_or_default();
}
#[pyo3(signature = (clock_rate=None))]
@@ -391,7 +392,13 @@ impl PyPerformance {
}
fn as_difficulty(&self) -> Difficulty {
let mut difficulty = Difficulty::new().mods(self.mods);
let mut difficulty = Difficulty::new();
difficulty = match self.mods {
PyGameMods::Lazer(ref mods) => difficulty.mods(mods.clone()),
PyGameMods::Intermode(ref mods) => difficulty.mods(mods),
PyGameMods::Legacy(mods) => difficulty.mods(mods),
};
if let Some(passed_objects) = self.passed_objects {
difficulty = difficulty.passed_objects(passed_objects);