[−][src]Module tbot::state
The stateful event loop and utilities for it.
The stateful event loop makes it easier to write bots that depend on some kind of state. It can be a chatting bot, a quiz bot or any other bot that needs to store data. The stateful event loop also can be used to easily share some data between all handlers, e.g. if you use a database that stores all your data, you can implement an utility struct with methods that simplify access to the database and share it across all your handlers.
For example, let's think of a bot with a global counter, and the bot allows increasing and showing it.
If we'd have gone with a stateless event loop, we'd start with this:
let mut bot = tbot::from_env!("BOT_TOKEN").event_loop();
To go with a stateful bot, we simply call stateful_event_loop
instead of
event_loop
:
let mut bot = tbot::from_env!("BOT_TOKEN") .stateful_event_loop(initial_state);
What should initial_state
be? It can actually be any value (that can be
shared across threads). You'd think that, as we only have a counter,
we would simply use any integer type. However, tbot
won't let you mutate
the state — instead, you should make use of interior mutability yourself.
This design decision was made to avoid mutability if it isn't needed and
for users to decide what parts of their state should be mutable — that is
to prevent all the state being locked when only a part of it is actually
needed to be locked.
So what we need is an integer wrapped in an RwLock
:
use tokio::sync::RwLock; let mut bot = tbot::from_env!("BOT_TOKEN") .stateful_event_loop(RwLock::new(0));
Now, if we would have gone with the stateless event loop, we'd write this:
bot.command("increase", |context| async move { /* .. */ });
Once we opt in to the stateful event loop, we need to write this:
bot.command("increase", |context, state| async move { /* .. */ });
The state is passed being wrapped in an Arc
, that is, this handler
receives Arc<RwLock<i32>>
as the second argument. This allows concurrent
access to the state. Now you only need to use the state:
use tbot::prelude::*; bot.command("increase", |context, state| async move { *state.write().await += 1; let call_result = context.send_message("Increased the counter").call().await; if let Err(err) = call_result { dbg!(err); } });
tbot
also provides a few utility state stores for common patterns.
You can combine them with other state stores or with your state if needed.
Re-exports
pub use chats::Chats; |
pub use messages::Messages; |
Modules
chats | A store for state per chat. |
messages | A store for state per message. |
Structs
StatefulEventLoop | A stateful event loop. |