Skip to content

Commit 87b95e8

Browse files
committed
Split PageCursor out of VoicePaginator
1 parent 889370c commit 87b95e8

8 files changed

Lines changed: 154 additions & 32 deletions

File tree

Cargo.lock

Lines changed: 8 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,6 @@ workspace = true
2828
[dependencies]
2929
const_format = "0.2"
3030
jemallocator = "0.5.4"
31-
small-fixed-array = { version = "0.4.5", features = [
32-
"nightly",
33-
"to-arraystring",
34-
] }
35-
3631
rand.workspace = true
3732
bytes.workspace = true
3833
serde.workspace = true
@@ -46,6 +41,7 @@ dashmap.workspace = true
4641
mini-moka.workspace = true
4742
parking_lot.workspace = true
4843
tokio-tungstenite.workspace = true
44+
small-fixed-array.workspace = true
4945

5046
tts_core = { path = "tts_core" }
5147
tts_tasks = { path = "tts_tasks" }
@@ -68,6 +64,7 @@ parking_lot = "0.12"
6864
mini-moka = { version = "0.10.3", features = ["sync"] }
6965
serde_json = { version = "1.0.149", default-features = false }
7066
tokio-tungstenite = { version = "0.28.0", default-features = false }
67+
small-fixed-array = { version = "0.4.5", features = ["nightly", "to-arraystring"] }
7168
# TODO: Remove `dashmap` once mini_moka releases a breaking version with dashmap 6.
7269
typesize = { version = "0.1.9", features = ["arrayvec", "dashmap", "details"] }
7370

flake.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
lockFile = ./Cargo.lock;
3030
outputHashes = {
3131
"poise-0.6.1" = "sha256-6NU1UOQUz8WO77Luv7VLp/RL1May65Y7JmMWxaPbgvo=";
32-
"serenity-0.12.5" = "sha256-V5FxH5DlNqPE0Eb76y5zL6ZjzX4q52H2hspqaoOGeQA=";
32+
"serenity-0.12.5" = "sha256-tonM8Ixc1dLRmNDjPX54gsyimMLWTiB0/LMHHfQtpNo=";
3333
};
3434
};
3535

@@ -38,8 +38,10 @@
3838
mold
3939
];
4040

41-
doCheck = false;
4241
hardeningDisable = [ "fortify" ];
42+
checkPhase = ''
43+
cargo test -p tts_commands
44+
'';
4345
};
4446
in
4547
tts-utils.mkTTSModule {

tts_commands/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ serenity.workspace = true
2121
typesize.workspace = true
2222
arrayvec.workspace = true
2323
parking_lot.workspace = true
24+
small-fixed-array.workspace = true
2425

2526
tts_core = { path = "../tts_core" }
2627

tts_commands/src/settings/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ mod owner;
22
mod setup;
33
mod voice_paginator;
44

5-
use std::{borrow::Cow, collections::HashMap, fmt::Write, sync::atomic::Ordering};
5+
use std::{borrow::Cow, collections::BTreeMap, fmt::Write, sync::atomic::Ordering};
66

77
use aformat::{ToArrayString, aformat};
88
use arrayvec::ArrayString;
@@ -1209,7 +1209,7 @@ async fn list_polly_voices(ctx: &Context<'_>) -> Result<(String, Box<[String]>)>
12091209
_ => &data.polly_voices[TTSMode::Polly.default_voice()],
12101210
};
12111211

1212-
let mut lang_to_voices: HashMap<_, Vec<_>> = HashMap::new();
1212+
let mut lang_to_voices: BTreeMap<_, Vec<_>> = BTreeMap::new();
12131213
for voice in data.polly_voices.values() {
12141214
lang_to_voices
12151215
.entry(&voice.language_name)
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use std::num::Saturating;
2+
3+
use tracing::warn;
4+
5+
#[derive(Debug)]
6+
pub(super) struct PageCursor {
7+
pages: Box<[String]>,
8+
index: Saturating<usize>,
9+
}
10+
11+
impl PageCursor {
12+
pub fn new(pages: Box<[String]>) -> Self {
13+
Self {
14+
pages,
15+
index: Saturating(0),
16+
}
17+
}
18+
19+
pub fn jump_start(&mut self) {
20+
self.index = Saturating(0);
21+
}
22+
23+
pub fn can_rewind(&self) -> bool {
24+
self.index != Saturating(0)
25+
}
26+
27+
pub fn rewind(&mut self) {
28+
self.index -= 1;
29+
}
30+
31+
pub fn current(&self) -> &str {
32+
if let Some(page) = self.pages.get(self.index.0) {
33+
page
34+
} else {
35+
warn!("Ran off the end of the pages: {self:?}");
36+
""
37+
}
38+
}
39+
40+
pub fn can_advance(&self) -> bool {
41+
self.index != Saturating(self.pages.len() - 1)
42+
}
43+
44+
pub fn advance(&mut self) {
45+
self.index = (self.index + Saturating(1)).min(Saturating(self.pages.len() - 1));
46+
}
47+
48+
pub fn jump_end(&mut self) {
49+
self.index = Saturating(self.pages.len() - 1);
50+
}
51+
}
52+
53+
#[cfg(test)]
54+
mod tests {
55+
use crate::settings::voice_paginator::PageCursor;
56+
57+
fn make_pages() -> PageCursor {
58+
PageCursor::new(
59+
["a", "b", "c", "d", "e", "f"]
60+
.into_iter()
61+
.map(String::from)
62+
.collect(),
63+
)
64+
}
65+
66+
#[test]
67+
fn rewind_off_start() {
68+
let mut cursor = make_pages();
69+
for _ in 0..25 {
70+
cursor.rewind();
71+
}
72+
73+
assert_eq!(cursor.current(), "a");
74+
}
75+
76+
#[test]
77+
fn rewind_after_end() {
78+
let mut cursor = make_pages();
79+
assert!(!cursor.can_rewind());
80+
assert_eq!(cursor.current(), "a");
81+
for _ in 0..2 {
82+
cursor.jump_start();
83+
assert!(!cursor.can_rewind());
84+
assert_eq!(cursor.current(), "a");
85+
}
86+
for _ in 0..2 {
87+
cursor.rewind();
88+
assert!(!cursor.can_rewind());
89+
assert_eq!(cursor.current(), "a");
90+
}
91+
}
92+
93+
#[test]
94+
fn advance_past_start() {
95+
let mut cursor = make_pages();
96+
for _ in 0..25 {
97+
cursor.advance();
98+
}
99+
100+
assert_eq!(cursor.current(), "f");
101+
}
102+
103+
#[test]
104+
fn advance_after_end() {
105+
let mut cursor = make_pages();
106+
107+
assert!(cursor.can_advance());
108+
assert_eq!(cursor.current(), "a");
109+
for _ in 0..2 {
110+
cursor.jump_end();
111+
assert!(!cursor.can_advance());
112+
assert_eq!(cursor.current(), "f");
113+
}
114+
for _ in 0..2 {
115+
cursor.advance();
116+
assert!(!cursor.can_advance());
117+
assert_eq!(cursor.current(), "f");
118+
}
119+
}
120+
}

tts_commands/src/settings/voice_paginator.rs renamed to tts_commands/src/settings/voice_paginator/mod.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ use serenity::{CollectComponentInteractions, builder::*, small_fixed_array::Fixe
55

66
use tts_core::structs::{Context, TTSMode};
77

8+
use cursor::PageCursor;
9+
10+
mod cursor;
11+
812
pub struct MenuPaginator<'a> {
9-
index: usize,
1013
mode: TTSMode,
1114
ctx: Context<'a>,
12-
pages: Box<[String]>,
15+
pages: PageCursor,
1316
footer: Cow<'a, str>,
1417
current_voice: String,
1518
}
@@ -24,11 +27,10 @@ impl<'a> MenuPaginator<'a> {
2427
) -> Self {
2528
Self {
2629
ctx,
27-
pages,
2830
current_voice,
2931
mode,
3032
footer,
31-
index: 0,
33+
pages: PageCursor::new(pages),
3234
}
3335
}
3436

@@ -55,9 +57,8 @@ impl<'a> MenuPaginator<'a> {
5557
))
5658
.disabled(
5759
disabled
58-
|| (["⏮️", "◀"].contains(&emoji) && self.index == 0)
59-
|| (["▶️", "⏭️"].contains(&emoji)
60-
&& self.index == (self.pages.len() - 1)),
60+
|| (["⏮️", "◀"].contains(&emoji) && !self.pages.can_rewind())
61+
|| (["▶️", "⏭️"].contains(&emoji) && !self.pages.can_advance()),
6162
)
6263
})
6364
.collect();
@@ -68,7 +69,7 @@ impl<'a> MenuPaginator<'a> {
6869
async fn create_message(&self) -> serenity::Result<serenity::MessageId> {
6970
let components = [self.create_action_row(false)];
7071
let builder = poise::CreateReply::default()
71-
.embed(self.create_page(&self.pages[self.index]))
72+
.embed(self.create_page(self.pages.current()))
7273
.components(&components);
7374

7475
self.ctx.send(builder).await?.message().await.map(|m| m.id)
@@ -81,7 +82,7 @@ impl<'a> MenuPaginator<'a> {
8182
) -> serenity::Result<()> {
8283
let components = [self.create_action_row(disable)];
8384
let builder = CreateInteractionResponseMessage::default()
84-
.embed(self.create_page(&self.pages[self.index]))
85+
.embed(self.create_page(self.pages.current()))
8586
.components(&components);
8687

8788
let response = CreateInteractionResponse::UpdateMessage(builder);
@@ -104,22 +105,22 @@ impl<'a> MenuPaginator<'a> {
104105

105106
match interaction.data.custom_id.as_str() {
106107
"⏮️" => {
107-
self.index = 0;
108+
self.pages.jump_start();
108109
self.edit_message(interaction, false).await?;
109110
}
110111
"◀" => {
111-
self.index = self.index.saturating_sub(1);
112+
self.pages.rewind();
112113
self.edit_message(interaction, false).await?;
113114
}
114115
"⏹️" => {
115116
return self.edit_message(interaction, true).await;
116117
}
117118
"▶️" => {
118-
self.index = self.index.saturating_add(1).max(self.pages.len() - 1);
119+
self.pages.advance();
119120
self.edit_message(interaction, false).await?;
120121
}
121122
"⏭️" => {
122-
self.index = self.pages.len() - 1;
123+
self.pages.jump_end();
123124
self.edit_message(interaction, false).await?;
124125
}
125126
_ => unreachable!(),

0 commit comments

Comments
 (0)