chore: change project layout
This commit is contained in:
1
BallanceTasSonnet/rust/.gitignore
vendored
Normal file
1
BallanceTasSonnet/rust/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
247
BallanceTasSonnet/rust/Cargo.lock
generated
Normal file
247
BallanceTasSonnet/rust/Cargo.lock
generated
Normal file
@@ -0,0 +1,247 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "BallanceTasSonnet"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"libz-sys",
|
||||
"pyo3",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.180"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab53c047fcd1a1d2a8820fe84f05d6be69e9526be40cb03b73f86b6b03e6d87d"
|
||||
dependencies = [
|
||||
"indoc",
|
||||
"libc",
|
||||
"memoffset",
|
||||
"once_cell",
|
||||
"portable-atomic",
|
||||
"pyo3-build-config",
|
||||
"pyo3-ffi",
|
||||
"pyo3-macros",
|
||||
"unindent",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-build-config"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b455933107de8642b4487ed26d912c2d899dec6114884214a0b3bb3be9261ea6"
|
||||
dependencies = [
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-ffi"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c85c9cbfaddf651b1221594209aed57e9e5cff63c4d11d1feead529b872a089"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pyo3-build-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a5b10c9bf9888125d917fb4d2ca2d25c8df94c7ab5a52e13313a07e050a3b02"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-macros-backend",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros-backend"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03b51720d314836e53327f5871d4c0cfb4fb37cc2c4a11cc71907a86342c40f9"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"pyo3-build-config",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "unindent"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
15
BallanceTasSonnet/rust/Cargo.toml
Normal file
15
BallanceTasSonnet/rust/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "BallanceTasSonnet"
|
||||
version = "1.0.0"
|
||||
edition = "2024"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[lib]
|
||||
name = "_blctas"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
pyo3 = "0.27.0"
|
||||
thiserror = "2.0.12"
|
||||
byteorder = "1.5.0"
|
||||
libz-sys = "1.1.23"
|
||||
351
BallanceTasSonnet/rust/src/lib.rs
Normal file
351
BallanceTasSonnet/rust/src/lib.rs
Normal file
@@ -0,0 +1,351 @@
|
||||
use pyo3::prelude::*;
|
||||
|
||||
pub(crate) mod wrapped;
|
||||
|
||||
#[pymodule(name = "_blctas")]
|
||||
/// Provides functionality for handling Ballance TAS works.
|
||||
mod blctas {
|
||||
#[pymodule_export]
|
||||
use super::tasfile;
|
||||
}
|
||||
|
||||
#[pymodule(submodule)]
|
||||
/// Provides functionality for handling Ballance TAS files loading, saving and editing.
|
||||
mod tasfile {
|
||||
use pyo3::{exceptions::PyRuntimeError, prelude::*};
|
||||
use crate::wrapped::tasfile::{
|
||||
Error as RsTasError, TasFile as RsTasFile, TasFrame as RsTasFrame, TasKey as RsTasKey
|
||||
};
|
||||
|
||||
impl From<RsTasError> for PyErr {
|
||||
fn from(error: RsTasError) -> Self {
|
||||
PyRuntimeError::new_err(error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(eq, eq_int)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
/// Represents the different keys that can be pressed in a TAS frame.
|
||||
enum TasKey {
|
||||
#[pyo3(name = "KEY_UP")]
|
||||
/// Up arrow key
|
||||
KeyUp,
|
||||
#[pyo3(name = "KEY_DOWN")]
|
||||
/// Down arrow key
|
||||
KeyDown,
|
||||
#[pyo3(name = "KEY_LEFT")]
|
||||
/// Left arrow key
|
||||
KeyLeft,
|
||||
#[pyo3(name = "KEY_RIGHT")]
|
||||
/// Right arrow key
|
||||
KeyRight,
|
||||
#[pyo3(name = "KEY_SHIFT")]
|
||||
/// Shift key
|
||||
KeyShift,
|
||||
#[pyo3(name = "KEY_SPACE")]
|
||||
/// Spacebar key
|
||||
KeySpace,
|
||||
#[pyo3(name = "KEY_Q")]
|
||||
/// Q key
|
||||
KeyQ,
|
||||
#[pyo3(name = "KEY_ESC")]
|
||||
/// Escape key
|
||||
KeyEsc,
|
||||
#[pyo3(name = "KEY_ENTER")]
|
||||
/// Enter key
|
||||
KeyEnter,
|
||||
}
|
||||
|
||||
impl From<TasKey> for RsTasKey {
|
||||
fn from(key: TasKey) -> RsTasKey {
|
||||
match key {
|
||||
TasKey::KeyUp => RsTasKey::KeyUp,
|
||||
TasKey::KeyDown => RsTasKey::KeyDown,
|
||||
TasKey::KeyLeft => RsTasKey::KeyLeft,
|
||||
TasKey::KeyRight => RsTasKey::KeyRight,
|
||||
TasKey::KeyShift => RsTasKey::KeyShift,
|
||||
TasKey::KeySpace => RsTasKey::KeySpace,
|
||||
TasKey::KeyQ => RsTasKey::KeyQ,
|
||||
TasKey::KeyEsc => RsTasKey::KeyEsc,
|
||||
TasKey::KeyEnter => RsTasKey::KeyEnter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass]
|
||||
#[derive(Debug)]
|
||||
/// Represents a TAS file containing a sequence of frames.
|
||||
/// Each frame contains key press information and delta time for Ballance gameplay.
|
||||
struct TasFile {
|
||||
inner: RsTasFile
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl TasFile {
|
||||
// region: Status
|
||||
|
||||
/// Clears all frames from the TAS file.
|
||||
fn clear(&mut self) -> PyResult<()> {
|
||||
Ok(self.inner.clear())
|
||||
}
|
||||
|
||||
/// Gets the number of frames in the TAS file.
|
||||
///
|
||||
/// Returns:
|
||||
/// The count of frames in the TAS file.
|
||||
fn get_count(&self) -> PyResult<usize> {
|
||||
Ok(self.inner.get_count())
|
||||
}
|
||||
|
||||
/// Checks if the TAS file is empty (contains no frames).
|
||||
///
|
||||
/// Returns:
|
||||
/// True if the TAS file is empty, False otherwise.
|
||||
fn is_empty(&self) -> PyResult<bool> {
|
||||
Ok(self.inner.is_empty())
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region: Single Operation
|
||||
|
||||
/// Gets the delta time of a frame at the specified index.
|
||||
///
|
||||
/// Args:
|
||||
/// index: The index of the frame to get the delta time from.
|
||||
///
|
||||
/// Returns:
|
||||
/// The delta time value of the frame at the specified index.
|
||||
///
|
||||
/// Raises:
|
||||
/// RuntimeError: If the index is out of range.
|
||||
fn get_delta_time(&self, index: usize) -> PyResult<f32> {
|
||||
Ok(self.inner.visit(index)?.get_delta_time())
|
||||
}
|
||||
|
||||
/// Sets the delta time of a frame at the specified index.
|
||||
///
|
||||
/// Args:
|
||||
/// index: The index of the frame to set the delta time for.
|
||||
/// delta_time: The new delta time value to set.
|
||||
///
|
||||
/// Raises:
|
||||
/// RuntimeError: If the index is out of range.
|
||||
fn set_delta_time(&mut self, index: usize, delta_time: f32) -> PyResult<()> {
|
||||
Ok(self.inner.visit_mut(index)?.set_delta_time(delta_time))
|
||||
}
|
||||
|
||||
/// Checks if a specific key is pressed in the frame at the specified index.
|
||||
///
|
||||
/// Args:
|
||||
/// index: The index of the frame to check.
|
||||
/// key: The key to check for press status.
|
||||
///
|
||||
/// Returns:
|
||||
/// True if the key is pressed, False otherwise.
|
||||
///
|
||||
/// Raises:
|
||||
/// RuntimeError: If the index is out of range.
|
||||
fn is_key_pressed(&self, index: usize, key: TasKey) -> PyResult<bool> {
|
||||
Ok(self.inner.visit(index)?.is_key_pressed(key.into()))
|
||||
}
|
||||
|
||||
/// Sets the press status of a specific key in the frame at the specified index.
|
||||
///
|
||||
/// Args:
|
||||
/// index: The index of the frame to modify.
|
||||
/// key: The key to set the press status for.
|
||||
/// pressed: True to press the key, False to release it.
|
||||
///
|
||||
/// Raises:
|
||||
/// RuntimeError: If the index is out of range.
|
||||
fn set_key_pressed(&mut self, index: usize, key: TasKey, pressed: bool) -> PyResult<()> {
|
||||
Ok(self.inner.visit_mut(index)?.set_key_pressed(key.into(), pressed))
|
||||
}
|
||||
|
||||
/// Flips the press status of a specific key in the frame at the specified index.
|
||||
/// If the key was pressed, it becomes released; if it was released, it becomes pressed.
|
||||
///
|
||||
/// Args:
|
||||
/// index: The index of the frame to modify.
|
||||
/// key: The key to flip the press status for.
|
||||
///
|
||||
/// Raises:
|
||||
/// RuntimeError: If the index is out of range.
|
||||
fn flip_key_pressed(&mut self, index: usize, key: TasKey) -> PyResult<()> {
|
||||
Ok(self.inner.visit_mut(index)?.flip_key_pressed(key.into()))
|
||||
}
|
||||
|
||||
/// Clears all key presses in the frame at the specified index, setting all keys to released state.
|
||||
///
|
||||
/// Args:
|
||||
/// index: The index of the frame to clear key presses for.
|
||||
///
|
||||
/// Raises:
|
||||
/// RuntimeError: If the index is out of range.
|
||||
fn clear_key_pressed(&mut self, index: usize) -> PyResult<()> {
|
||||
Ok(self.inner.visit_mut(index)?.clear_key_pressed())
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region: Batchly Operation
|
||||
|
||||
/// Sets the delta time for a range of frames from index_from to index_to (inclusive).
|
||||
/// This is a batch operation that modifies multiple frames in one call, which is more
|
||||
/// efficient than calling set_delta_time individually on each frame.
|
||||
///
|
||||
/// Args:
|
||||
/// index_from: The starting index (inclusive) of the range to modify.
|
||||
/// index_to: The ending index (inclusive) of the range to modify.
|
||||
/// delta_time: The new delta time value to set for all frames in the range.
|
||||
///
|
||||
/// Raises:
|
||||
/// RuntimeError: If either index is out of range or if index_to < index_from.
|
||||
fn batchly_set_delta_time(&mut self, index_from: usize, index_to: usize, delta_time: f32) -> PyResult<()> {
|
||||
for frame in self.inner.batchly_visit_mut(index_from, index_to)? {
|
||||
frame.set_delta_time(delta_time);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the press status of a specific key for a range of frames from index_from to index_to (inclusive).
|
||||
/// This is a batch operation that modifies multiple frames in one call, which is more
|
||||
/// efficient than calling set_key_pressed individually on each frame.
|
||||
///
|
||||
/// Args:
|
||||
/// index_from: The starting index (inclusive) of the range to modify.
|
||||
/// index_to: The ending index (inclusive) of the range to modify.
|
||||
/// key: The key to set the press status for.
|
||||
/// pressed: True to press the key, False to release it for all frames in the range.
|
||||
///
|
||||
/// Raises:
|
||||
/// RuntimeError: If either index is out of range or if index_to < index_from.
|
||||
fn batchly_set_key_pressed(&mut self, index_from: usize, index_to: usize, key: TasKey, pressed: bool) -> PyResult<()> {
|
||||
for frame in self.inner.batchly_visit_mut(index_from, index_to)? {
|
||||
frame.set_key_pressed(key.into(), pressed);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Flips the press status of a specific key for a range of frames from index_from to index_to (inclusive).
|
||||
/// This is a batch operation that modifies multiple frames in one call, which is more
|
||||
/// efficient than calling flip_key_pressed individually on each frame.
|
||||
///
|
||||
/// Args:
|
||||
/// index_from: The starting index (inclusive) of the range to modify.
|
||||
/// index_to: The ending index (inclusive) of the range to modify.
|
||||
/// key: The key to flip the press status for in all frames in the range.
|
||||
///
|
||||
/// Raises:
|
||||
/// RuntimeError: If either index is out of range or if index_to < index_from.
|
||||
fn batchly_flip_key_pressed(&mut self, index_from: usize, index_to: usize, key: TasKey) -> PyResult<()> {
|
||||
for frame in self.inner.batchly_visit_mut(index_from, index_to)? {
|
||||
frame.flip_key_pressed(key.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Clears all key presses for a range of frames from index_from to index_to (inclusive).
|
||||
/// This sets all keys to the released state for all frames in the range.
|
||||
/// This is a batch operation that modifies multiple frames in one call, which is more
|
||||
/// efficient than calling clear_key_pressed individually on each frame.
|
||||
///
|
||||
/// Args:
|
||||
/// index_from: The starting index (inclusive) of the range to modify.
|
||||
/// index_to: The ending index (inclusive) of the range to modify.
|
||||
///
|
||||
/// Raises:
|
||||
/// RuntimeError: If either index is out of range or if index_to < index_from.
|
||||
fn batchly_clear_key_pressed(&mut self, index_from: usize, index_to: usize) -> PyResult<()> {
|
||||
for frame in self.inner.batchly_visit_mut(index_from, index_to)? {
|
||||
frame.clear_key_pressed();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region: Modify
|
||||
|
||||
/// Appends a specified number of frames with the given delta time to the end of the TAS file.
|
||||
///
|
||||
/// Args:
|
||||
/// count: The number of frames to append.
|
||||
/// delta_time: The delta time value for the new frames.
|
||||
fn append(&mut self, count: usize, delta_time: f32) -> PyResult<()> {
|
||||
let frames = vec![RsTasFrame::with_delta_time(delta_time); count];
|
||||
Ok(self.inner.append(&frames))
|
||||
}
|
||||
|
||||
/// Inserts a specified number of frames with the given delta time at the specified index.
|
||||
///
|
||||
/// Args:
|
||||
/// index: The position at which to insert the new frames.
|
||||
/// count: The number of frames to insert.
|
||||
/// delta_time: The delta time value for the new frames.
|
||||
///
|
||||
/// Raises:
|
||||
/// RuntimeError: If the index is out of range.
|
||||
fn insert(&mut self, index: usize, count: usize, delta_time: f32) -> PyResult<()> {
|
||||
let frames = vec![RsTasFrame::with_delta_time(delta_time); count];
|
||||
Ok(self.inner.insert(index, &frames)?)
|
||||
}
|
||||
|
||||
/// Removes frames from the TAS file within the specified range (inclusive).
|
||||
///
|
||||
/// Args:
|
||||
/// index_from: The starting index (inclusive) of the range to remove.
|
||||
/// index_to: The ending index (inclusive) of the range to remove.
|
||||
///
|
||||
/// Raises:
|
||||
/// RuntimeError: If either index is out of range or if index_to < index_from.
|
||||
fn remove(&mut self, index_from: usize, index_to: usize) -> PyResult<()> {
|
||||
Ok(self.inner.remove(index_from, index_to)?)
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
/// Creates a new TAS file with a specified number of frames, all having the same delta time.
|
||||
///
|
||||
/// Args:
|
||||
/// count: The number of frames to create in the new TAS file.
|
||||
/// delta_time: The delta time value for all frames in the new TAS file.
|
||||
///
|
||||
/// Returns:
|
||||
/// A new TasFile instance with the specified number of frames.
|
||||
fn create(count: usize, delta_time: f32) -> PyResult<TasFile> {
|
||||
Ok(TasFile { inner: RsTasFile::new(vec![RsTasFrame::with_delta_time(delta_time); count]) })
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
/// Loads a TAS file from disk.
|
||||
///
|
||||
/// Args:
|
||||
/// filename: The path to the TAS file to load.
|
||||
///
|
||||
/// Returns:
|
||||
/// A TasFile instance loaded from the specified file.
|
||||
///
|
||||
/// Raises:
|
||||
/// RuntimeError: If the file cannot be loaded or is invalid.
|
||||
fn load(filename: &str) -> PyResult<TasFile> {
|
||||
Ok(TasFile { inner: RsTasFile::load(filename)? })
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
/// Saves a TAS file to disk.
|
||||
///
|
||||
/// Args:
|
||||
/// file: The TasFile instance to save.
|
||||
/// filename: The path where the TAS file should be saved.
|
||||
///
|
||||
/// Raises:
|
||||
/// RuntimeError: If the file cannot be saved.
|
||||
fn save(file: &TasFile, filename: &str) -> PyResult<()> {
|
||||
Ok(file.inner.save(filename)?)
|
||||
}
|
||||
|
||||
}
|
||||
1
BallanceTasSonnet/rust/src/wrapped.rs
Normal file
1
BallanceTasSonnet/rust/src/wrapped.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub(crate) mod tasfile;
|
||||
337
BallanceTasSonnet/rust/src/wrapped/tasfile.rs
Normal file
337
BallanceTasSonnet/rust/src/wrapped/tasfile.rs
Normal file
@@ -0,0 +1,337 @@
|
||||
use byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt};
|
||||
use libz_sys;
|
||||
use std::ffi::{c_int, c_ulong};
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::mem::MaybeUninit;
|
||||
use thiserror::Error as TeError;
|
||||
|
||||
#[derive(Debug, TeError)]
|
||||
pub enum Error {
|
||||
#[error("given index is out of range")]
|
||||
IndexOutOfRange,
|
||||
#[error("arithmetic overflow")]
|
||||
NumOverflow,
|
||||
#[error("fail to cast numeric value")]
|
||||
BadNumCast,
|
||||
|
||||
#[error("fail to read or write file")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("fail to call zlib function")]
|
||||
ZlibCall,
|
||||
#[error("given TAS file is wrong")]
|
||||
BadTasFile,
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum TasKey {
|
||||
KeyUp,
|
||||
KeyDown,
|
||||
KeyLeft,
|
||||
KeyRight,
|
||||
KeyShift,
|
||||
KeySpace,
|
||||
KeyQ,
|
||||
KeyEsc,
|
||||
KeyEnter,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||
#[repr(C)]
|
||||
pub struct TasFrame {
|
||||
delta_time: f32,
|
||||
key_flags: u32,
|
||||
}
|
||||
|
||||
impl TasFrame {
|
||||
pub fn new(delta_time: f32, key_flags: u32) -> Self {
|
||||
Self {
|
||||
delta_time,
|
||||
key_flags,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_delta_time(delta_time: f32) -> Self {
|
||||
Self::new(delta_time, 0u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl TasFrame {
|
||||
pub fn get_delta_time(&self) -> f32 {
|
||||
self.delta_time
|
||||
}
|
||||
|
||||
pub fn set_delta_time(&mut self, delta_time: f32) -> () {
|
||||
self.delta_time = delta_time
|
||||
}
|
||||
}
|
||||
|
||||
impl TasFrame {
|
||||
fn get_key_flag(key: TasKey) -> u32 {
|
||||
let bit = match key {
|
||||
TasKey::KeyUp => 0,
|
||||
TasKey::KeyDown => 1,
|
||||
TasKey::KeyLeft => 2,
|
||||
TasKey::KeyRight => 3,
|
||||
TasKey::KeyShift => 4,
|
||||
TasKey::KeySpace => 5,
|
||||
TasKey::KeyQ => 6,
|
||||
TasKey::KeyEsc => 7,
|
||||
TasKey::KeyEnter => 8,
|
||||
};
|
||||
1u32 << bit
|
||||
}
|
||||
|
||||
pub fn is_key_pressed(&self, key: TasKey) -> bool {
|
||||
(self.key_flags & Self::get_key_flag(key)) != 0u32
|
||||
}
|
||||
|
||||
pub fn set_key_pressed(&mut self, key: TasKey, pressed: bool) {
|
||||
if pressed {
|
||||
self.key_flags |= Self::get_key_flag(key)
|
||||
} else {
|
||||
self.key_flags &= !(Self::get_key_flag(key))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flip_key_pressed(&mut self, key: TasKey) {
|
||||
self.key_flags ^= Self::get_key_flag(key)
|
||||
}
|
||||
|
||||
pub fn clear_key_pressed(&mut self) {
|
||||
self.key_flags = 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TasFile {
|
||||
frames: Vec<TasFrame>,
|
||||
}
|
||||
|
||||
impl TasFile {
|
||||
pub fn new(frames: Vec<TasFrame>) -> Self {
|
||||
Self { frames }
|
||||
}
|
||||
|
||||
pub fn load(filename: &str) -> Result<Self> {
|
||||
// Open file
|
||||
let mut reader = File::open(filename)?;
|
||||
|
||||
// Read decompressed size.
|
||||
let u32_decomp_size = reader.read_u32::<NativeEndian>()?;
|
||||
let usize_decomp_size = usize::try_from(u32_decomp_size).map_err(|_| Error::BadNumCast)?;
|
||||
let culong_decomp_size =
|
||||
c_ulong::try_from(usize_decomp_size).map_err(|_| Error::BadNumCast)?;
|
||||
|
||||
// Check size and compute frame count.
|
||||
let frame_size = size_of::<TasFrame>();
|
||||
let frame_count = usize_decomp_size
|
||||
.checked_div(frame_size)
|
||||
.ok_or(Error::NumOverflow)?;
|
||||
if !usize_decomp_size.is_multiple_of(frame_size) {
|
||||
return Err(Error::BadTasFile);
|
||||
}
|
||||
|
||||
// Read all rest file into memory
|
||||
let mut comp_buffer = Vec::new();
|
||||
reader.read_to_end(&mut comp_buffer)?;
|
||||
// Get compressed buffer size.
|
||||
let usize_comp_size = comp_buffer.len();
|
||||
let culong_comp_size = c_ulong::try_from(usize_comp_size).map_err(|_| Error::BadNumCast)?;
|
||||
|
||||
// Create decompressed buffer with uninitialized memory
|
||||
let mut decomp_buffer: Box<[MaybeUninit<TasFrame>]> = Box::new_uninit_slice(frame_count);
|
||||
|
||||
// Decompress data
|
||||
let source = comp_buffer.as_ptr() as *const libz_sys::Bytef;
|
||||
let source_len = culong_comp_size;
|
||||
let dest = decomp_buffer.as_mut_ptr() as *mut libz_sys::Bytef;
|
||||
let mut dest_len = culong_decomp_size;
|
||||
let rv = unsafe { libz_sys::uncompress(dest, &mut dest_len, source, source_len) };
|
||||
if rv != libz_sys::Z_OK {
|
||||
return Err(Error::ZlibCall);
|
||||
}
|
||||
|
||||
// Convert uninitialized buffer to initialized
|
||||
// SAFETY: We've just initialized the buffer with uncompress
|
||||
let frames = unsafe {
|
||||
std::slice::from_raw_parts(decomp_buffer.as_ptr() as *const TasFrame, frame_count)
|
||||
.to_vec()
|
||||
};
|
||||
|
||||
// Okey
|
||||
Ok(Self::new(frames))
|
||||
}
|
||||
|
||||
pub fn save(&self, filename: &str) -> Result<()> {
|
||||
// Open file
|
||||
let mut writer = File::create(filename)?;
|
||||
|
||||
// Get decompressed size.
|
||||
let usize_decomp_size = size_of::<TasFrame>()
|
||||
.checked_mul(self.frames.len())
|
||||
.ok_or(Error::NumOverflow)?;
|
||||
|
||||
// Write decompressed size.
|
||||
let u32_decomp_size = u32::try_from(usize_decomp_size).map_err(|_| Error::BadNumCast)?;
|
||||
writer.write_u32::<NativeEndian>(u32_decomp_size)?;
|
||||
|
||||
// Get compressed buffer boundary
|
||||
let culong_decomp_size =
|
||||
c_ulong::try_from(usize_decomp_size).map_err(|_| Error::BadNumCast)?;
|
||||
let culong_comp_bound_size: c_ulong =
|
||||
unsafe { libz_sys::compressBound(culong_decomp_size) };
|
||||
|
||||
// Create buffer for it.
|
||||
let usize_comp_bound_size =
|
||||
usize::try_from(culong_comp_bound_size).map_err(|_| Error::BadNumCast)?;
|
||||
let mut buffer: Box<[MaybeUninit<u8>]> = Box::new_uninit_slice(usize_comp_bound_size);
|
||||
|
||||
// Compress data into buffer
|
||||
let source = self.frames.as_ptr() as *const libz_sys::Bytef;
|
||||
let source_len: c_ulong = culong_decomp_size;
|
||||
let dest = buffer.as_mut_ptr() as *mut libz_sys::Bytef;
|
||||
let mut dest_len: c_ulong = culong_comp_bound_size;
|
||||
let level: c_int = 9;
|
||||
let rv = unsafe { libz_sys::compress2(dest, &mut dest_len, source, source_len, level) };
|
||||
if rv != libz_sys::Z_OK {
|
||||
return Err(Error::ZlibCall);
|
||||
}
|
||||
|
||||
// Fetch the final compressed length.
|
||||
let culong_comp_size: c_ulong = dest_len;
|
||||
let usize_comp_size: usize =
|
||||
usize::try_from(culong_comp_size).map_err(|_| Error::BadNumCast)?;
|
||||
|
||||
// Write compressed data
|
||||
let buffer = unsafe { buffer.assume_init() };
|
||||
writer.write(&buffer[..usize_comp_size])?;
|
||||
|
||||
// Okey
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TasFile {
|
||||
/// 清空存储结构。
|
||||
pub fn clear(&mut self) {
|
||||
self.frames.clear()
|
||||
}
|
||||
|
||||
/// 获取当前存储的TAS帧的个数。
|
||||
pub fn get_count(&self) -> usize {
|
||||
self.frames.len()
|
||||
}
|
||||
|
||||
/// 获取当前存储结构是不是空的。
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.frames.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl TasFile {
|
||||
fn check_index(&self, index: usize) -> bool {
|
||||
index < self.frames.len()
|
||||
}
|
||||
|
||||
fn check_index_range(&self, index_from: usize, index_to: usize) -> bool {
|
||||
// Check index relation
|
||||
if index_to < index_from {
|
||||
return false;
|
||||
}
|
||||
// Check index range
|
||||
if index_to >= self.frames.len() {
|
||||
return false;
|
||||
}
|
||||
// Okey
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 访问给定索引的帧。
|
||||
pub fn visit<'a>(&'a self, index: usize) -> Result<&'a TasFrame> {
|
||||
if self.check_index(index) {
|
||||
Ok(&self.frames[index])
|
||||
} else {
|
||||
Err(Error::IndexOutOfRange)
|
||||
}
|
||||
}
|
||||
|
||||
/// 以可变形式访问给定索引的值。
|
||||
pub fn visit_mut<'a>(&'a mut self, index: usize) -> Result<&'a mut TasFrame> {
|
||||
if self.check_index(index) {
|
||||
Ok(&mut self.frames[index])
|
||||
} else {
|
||||
Err(Error::IndexOutOfRange)
|
||||
}
|
||||
}
|
||||
|
||||
/// 访问给定索引范围内的帧。
|
||||
pub fn batchly_visit<'a>(&'a self, index_from: usize, index_to: usize) -> Result<&'a [TasFrame]> {
|
||||
if self.check_index_range(index_from, index_to) {
|
||||
Ok(&self.frames[index_from..=index_to])
|
||||
} else {
|
||||
Err(Error::IndexOutOfRange)
|
||||
}
|
||||
}
|
||||
|
||||
/// 以可变形式访问给定索引范围内的帧。
|
||||
pub fn batchly_visit_mut<'a>(&'a mut self, index_from: usize, index_to: usize) -> Result<&'a mut[TasFrame]> {
|
||||
if self.check_index_range(index_from, index_to) {
|
||||
Ok(&mut self.frames[index_from..=index_to])
|
||||
} else {
|
||||
Err(Error::IndexOutOfRange)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TasFile {
|
||||
/// 在结尾继续添加给的的帧序列。
|
||||
///
|
||||
/// `frames`为要插入的元素的切片。
|
||||
pub fn append(&mut self, frames: &[TasFrame]) {
|
||||
self.frames.extend_from_slice(frames)
|
||||
}
|
||||
|
||||
/// 在给定的索引**之前**插入给定的帧序列。
|
||||
///
|
||||
/// 按照此函数约定,如果要在头部插入数据,则可以通过指定0来实现。
|
||||
/// 然而对于在尾部插入数据,或在空的存储中插入数据,可以指定存储结构的长度来实现。
|
||||
/// 即指定最大Index + 1的值来实现。
|
||||
///
|
||||
/// `index`为要在前方插入数据的元素的索引。`frames`为要插入的元素的切片。
|
||||
pub fn insert(&mut self, index: usize, frames: &[TasFrame]) -> Result<()> {
|
||||
if index > self.frames.len() {
|
||||
Err(Error::IndexOutOfRange)
|
||||
} else if index == self.frames.len() {
|
||||
// Insert at tail
|
||||
self.frames.extend_from_slice(frames);
|
||||
Ok(())
|
||||
} else {
|
||||
// Insert at middle or head
|
||||
self.frames.splice(index..index, frames.iter().copied());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// 将给定范围内的帧移除。
|
||||
///
|
||||
/// `index_from`为要开始移除的单元的索引。`index_to`为最后一个要移除的单元的索引。
|
||||
/// `index_from`和`index_to`指向的帧均会被删除(端点inclusive模式)。
|
||||
/// `index_to`不能小于`index_from`。
|
||||
/// `index_from`和`index_to`均不能超过最大索引。
|
||||
pub fn remove(&mut self, index_from: usize, index_to: usize) -> Result<()> {
|
||||
// Check index relation
|
||||
if index_to < index_from {
|
||||
return Err(Error::IndexOutOfRange);
|
||||
}
|
||||
// Check index range
|
||||
if index_to >= self.frames.len() {
|
||||
return Err(Error::IndexOutOfRange);
|
||||
}
|
||||
// Perform remove
|
||||
self.frames.drain(index_from..=index_to);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user