Expand description
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 a utility struct with methods that simplify access to the database, and then 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 use the stateful event loop, 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 use interior mutability. 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 needs 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
parallel 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 storages for common patterns.
You can combine them with other state storages or with your own storage
if needed.