Simplify composite_state example

This commit is contained in:
Сырцев Вадим Игоревич 2024-08-29 08:17:16 +03:00
parent 294f3f5447
commit de34970e70
No known key found for this signature in database
GPG key ID: D581B7E10673309B

View file

@ -40,7 +40,7 @@
use teloxide::{ use teloxide::{
dispatching::{dialogue::InMemStorage, MessageFilterExt}, dispatching::{dialogue::InMemStorage, MessageFilterExt},
prelude::*, prelude::*,
types::{ChatId, Message}, types::Message,
}; };
type Bot = teloxide::Bot; type Bot = teloxide::Bot;
@ -68,17 +68,11 @@ enum GlobalState {
#[derive(Clone)] #[derive(Clone)]
enum UserSetup { enum UserSetup {
ReceiveFullName, ReceiveFullName,
ReceiveAge { full_name: String }, ReceiveAge { full_name: FullName },
} }
/// Helper struct to store only required information to answer messages and #[derive(Clone, derive_more::Display)]
/// reduce the size of the stack frames for handler functions struct FullName(pub String);
#[derive(Clone)]
struct IdsBundle {
chat_id: ChatId,
// Can be used to reply to messages or edit messages
// message_id: MessageId,
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
@ -95,50 +89,38 @@ async fn main() {
} }
fn schema() -> UpdateHandler { fn schema() -> UpdateHandler {
Update::filter_message() Update::filter_message().branch(
/* Message::filter_text()
Currently the size of the `Message` struct (for TBA 6.9) is 1936 bytes, it's insane to copy it entirely in every handler's stack. .enter_dialogue::<Message, Storage, GlobalState>()
So, here I introduce the `IdsBundle` which is 8 bytes in size, because all we need is a `chat_id`. .branch(
The similar thing can be applied to the `CallbackQuery` struct which is teloxide::filter_command::<Command, _>()
even bigger.. .branch(dptree::case![Command::Start].endpoint(ask_full_name)),
Take a look at this issue: https://github.com/teloxide/teloxide/issues/1118, maybe there will be )
more appropriate approach: `Arc<Message>` or similar. .branch(dptree::case![GlobalState::Idle].endpoint(handle_configured_user_message))
*/ .branch(
.map(|msg: Message| IdsBundle { chat_id: msg.chat.id }) // Its essential not to use
.branch( // `dptree::case![GlobalState::UserSetup(UserSetup::ReceiveFullName)]` directly,
Message::filter_text() // this won't work. Each nested enum requires it's own `branch`
.enter_dialogue::<Message, Storage, GlobalState>() // scope. Actually, each `dptree::case![..]` introduces the inner
.branch( // enum value to the `DependencyMap`, so there is an option
teloxide::filter_command::<Command, _>() // to branch on the inner values freely.
.branch(dptree::case![Command::Start].endpoint(ask_full_name)), dptree::case![GlobalState::UserSetup(_state)]
) .branch(
.branch(dptree::case![GlobalState::Idle].endpoint(handle_configured_user_message)) dptree::case![UserSetup::ReceiveFullName]
.branch( .map(|text: String| FullName(text))
/* .endpoint(ask_age),
Its essential not to use `dptree::case![GlobalState::UserSetup(UserSetup::ReceiveFullName)]` directly, this won't work. )
.branch(
Each nested enum requires it's own `branch` scope. dptree::case![UserSetup::ReceiveAge { full_name }]
Actually, each `dptree::case![..]` introduces the inner enum value to the `DependencyMap`, so there is an option .endpoint(finish_user_setup),
to branch on the inner values freely. ),
*/ )
dptree::case![GlobalState::UserSetup(_state)] .branch(dptree::endpoint(handle_unconfigured_user_message)),
.branch(dptree::case![UserSetup::ReceiveFullName].endpoint(ask_age)) )
.branch(
dptree::case![UserSetup::ReceiveAge { full_name }]
.endpoint(finish_user_setup),
),
)
.branch(dptree::endpoint(handle_unconfigured_user_message)),
)
} }
async fn ask_full_name( async fn ask_full_name(bot: Bot, dialogue: Dialogue, message: Message) -> HandlerResult {
bot: Bot, bot.send_message(message.chat.id, "Let's start! What's your full name?").await?;
dialogue: Dialogue,
// For the sake of interest, take a look at the size of the `Message` struct
IdsBundle { chat_id }: IdsBundle,
) -> HandlerResult {
bot.send_message(chat_id, "Let's start! What's your full name?").await?;
dialogue.update(GlobalState::UserSetup(UserSetup::ReceiveFullName)).await?; dialogue.update(GlobalState::UserSetup(UserSetup::ReceiveFullName)).await?;
Ok(()) Ok(())
} }
@ -146,21 +128,21 @@ async fn ask_full_name(
async fn ask_age( async fn ask_age(
bot: Bot, bot: Bot,
dialogue: Dialogue, dialogue: Dialogue,
IdsBundle { chat_id }: IdsBundle, message: Message,
full_name: String, full_name: FullName,
) -> HandlerResult { ) -> HandlerResult {
bot.send_message(chat_id, format!("Hi, {full_name}! How old are you?")).await?; bot.send_message(message.chat.id, format!("Hi, {full_name}! How old are you?")).await?;
dialogue.update(GlobalState::UserSetup(UserSetup::ReceiveAge { full_name })).await?; dialogue.update(GlobalState::UserSetup(UserSetup::ReceiveAge { full_name })).await?;
Ok(()) Ok(())
} }
async fn finish_user_setup(bot: Bot, dialogue: Dialogue, message: Message) -> HandlerResult { async fn finish_user_setup(
/* bot: Bot,
We did `Message::filter_text`, so it's safe to assume that this message contains text. dialogue: Dialogue,
Unfortunately, we can't get incoming text as the handler parameter, because it's message: Message,
shadowed by the `full_name` value from the `UserSetup::ReceiveAge {full_name}` state age: String,
*/ ) -> HandlerResult {
let _age = match message.text().unwrap().parse::<u8>() { let _age = match age.parse::<u8>() {
Ok(age) => age, Ok(age) => age,
Err(_err) => { Err(_err) => {
bot.send_message(message.chat.id, "Please, enter your age").await?; bot.send_message(message.chat.id, "Please, enter your age").await?;
@ -173,18 +155,12 @@ async fn finish_user_setup(bot: Bot, dialogue: Dialogue, message: Message) -> Ha
Ok(()) Ok(())
} }
async fn handle_configured_user_message( async fn handle_configured_user_message(bot: Bot, message: Message) -> HandlerResult {
bot: Bot, bot.send_message(message.chat.id, "Hi, configured user!").await?;
IdsBundle { chat_id }: IdsBundle,
) -> HandlerResult {
bot.send_message(chat_id, "Hi, configured user!").await?;
Ok(()) Ok(())
} }
async fn handle_unconfigured_user_message( async fn handle_unconfigured_user_message(bot: Bot, message: Message) -> HandlerResult {
bot: Bot, bot.send_message(message.chat.id, "Use /start to setup your account").await?;
IdsBundle { chat_id }: IdsBundle,
) -> HandlerResult {
bot.send_message(chat_id, "Use /start to setup your account").await?;
Ok(()) Ok(())
} }