feat: use rosu-mods (#9)
* use rosu-mods * mention mods in readme * use published rosu-pp * remove test remnants
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
/target
|
||||
expanded.rs
|
||||
output.rs
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
|
||||
Generated
+35
-3
@@ -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]]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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: ...
|
||||
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -28,6 +28,7 @@ mod difficulty;
|
||||
mod error;
|
||||
mod gradual;
|
||||
mod mode;
|
||||
mod mods;
|
||||
mod performance;
|
||||
mod score_state;
|
||||
mod strains;
|
||||
|
||||
+731
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user