Skip to content

Commit fa7a3fb

Browse files
jamesbt365GnomedDev
authored andcommitted
Rework the EventHandler trait to have a single dispatch function (#3324)
The primary motivation for this change is to allow for concurrent execution of the event handler and the framework, but also brings other benefits: - The trait becomes much simpler, only having three functions now. - New fields to events will no longer be breaking changes.
1 parent fea412f commit fa7a3fb

File tree

25 files changed

+941
-1141
lines changed

25 files changed

+941
-1141
lines changed

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ Serenity supports bot login via the use of [`Client::builder`].
1616
You may also check your tokens prior to login via the use of
1717
[`validate_token`].
1818

19-
Once logged in, you may add handlers to your client to dispatch [`Event`]s,
20-
by implementing the handlers in a trait, such as [`EventHandler::message`].
21-
This will cause your handler to be called when a [`Event::MessageCreate`] is
22-
received. Each handler is given a [`Context`], giving information about the
23-
event. See the [client's module-level documentation].
19+
Once logged in, you can add an event handler to your client to dispatch [`Event`]s.
20+
This will allow you to recieve and handle events as you see fit. For example, an
21+
[`Event::MessageCreate`] event will be dispatched to you when a message is sent.
22+
Every event will give you access to a [`Context`], giving information about the event.
23+
See the [client's module-level documentation].
24+
2425

2526
The [`Shard`] is transparently handled by the library, removing
2627
unnecessary complexity. Sharded connections are automatically handled for
@@ -215,7 +216,6 @@ a Rust-native cloud development platform that allows deploying Serenity bots for
215216

216217
[`Cache`]: https://docs.rs/serenity/*/serenity/cache/struct.Cache.html
217218
[`Client::builder`]: https://docs.rs/serenity/*/serenity/client/struct.Client.html#method.builder
218-
[`EventHandler::message`]: https://docs.rs/serenity/*/serenity/client/trait.EventHandler.html#method.message
219219
[`Context`]: https://docs.rs/serenity/*/serenity/client/struct.Context.html
220220
[`Event`]: https://docs.rs/serenity/*/serenity/model/event/enum.Event.html
221221
[`Event::MessageCreate`]: https://docs.rs/serenity/*/serenity/model/event/enum.Event.html#variant.MessageCreate

examples/e01_basic_ping_bot/src/main.rs

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,44 @@
11
use serenity::async_trait;
2-
use serenity::model::channel::Message;
3-
use serenity::model::gateway::Ready;
2+
use serenity::gateway::client::FullEvent;
43
use serenity::prelude::*;
54

65
struct Handler;
76

87
#[async_trait]
98
impl EventHandler for Handler {
10-
// Set a handler for the `message` event. This is called whenever a new message is received.
11-
//
12-
// Event handlers are dispatched through a threadpool, and so multiple events can be dispatched
13-
// simultaneously.
14-
async fn message(&self, ctx: Context, msg: Message) {
15-
if msg.content == "!ping" {
16-
// Sending a message can fail, due to a network error, an authentication error, or lack
17-
// of permissions to post in the channel, so log to stdout when some error happens,
18-
// with a description of it.
19-
if let Err(why) = msg.channel_id.say(&ctx.http, "Pong!").await {
20-
println!("Error sending message: {why:?}");
21-
}
22-
}
23-
}
9+
async fn dispatch(&self, ctx: &Context, event: &FullEvent) {
10+
match event {
11+
// Set a handler for the `message` event. This is called whenever a new message is
12+
// received.
13+
//
14+
// Event handlers are dispatched through a threadpool, and so multiple events can be
15+
// dispatched simultaneously.
16+
FullEvent::Message {
17+
new_message, ..
18+
} => {
19+
if new_message.content == "!ping" {
20+
// Sending a message can fail, due to a network error, an authentication error,
21+
// or lack of permissions to post in the channel, so log to
22+
// stdout when some error happens, with a description of it.
23+
if let Err(why) = new_message.channel_id.say(&ctx.http, "Pong!").await {
24+
println!("Error sending message: {why:?}");
25+
}
26+
}
27+
},
2428

25-
// Set a handler to be called on the `ready` event. This is called when a shard is booted, and
26-
// a READY payload is sent by Discord. This payload contains data like the current user's guild
27-
// Ids, current user data, private channels, and more.
28-
//
29-
// In this case, just print what the current user's username is.
30-
async fn ready(&self, _: Context, ready: Ready) {
31-
println!("{} is connected!", ready.user.name);
29+
// Set a handler to be called on the `ready` event. This is called when a shard is
30+
// booted, and a READY payload is sent by Discord. This payload contains
31+
// data like the current user's guild Ids, current user data, private
32+
// channels, and more.
33+
//
34+
// In this case, just print what the current user's username is.
35+
FullEvent::Ready {
36+
data_about_bot, ..
37+
} => {
38+
println!("{} is connected!", data_about_bot.user.name);
39+
},
40+
_ => {},
41+
}
3242
}
3343
}
3444

examples/e02_transparent_guild_sharding/src/main.rs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use serenity::async_trait;
2-
use serenity::model::channel::Message;
3-
use serenity::model::gateway::Ready;
2+
use serenity::gateway::client::FullEvent;
43
use serenity::prelude::*;
54

65
// Serenity implements transparent sharding in a way that you do not need to handle separate
@@ -22,19 +21,25 @@ struct Handler;
2221

2322
#[async_trait]
2423
impl EventHandler for Handler {
25-
async fn message(&self, ctx: Context, msg: Message) {
26-
if msg.content == "!ping" {
27-
println!("Shard {}", ctx.shard_id);
28-
29-
if let Err(why) = msg.channel_id.say(&ctx.http, "Pong!").await {
30-
println!("Error sending message: {why:?}");
31-
}
24+
async fn dispatch(&self, ctx: &Context, event: &FullEvent) {
25+
match event {
26+
FullEvent::Message {
27+
new_message, ..
28+
} => {
29+
if new_message.content == "!ping" {
30+
if let Err(why) = new_message.channel_id.say(&ctx.http, "Pong!").await {
31+
println!("Error sending message: {why:?}");
32+
}
33+
}
34+
},
35+
FullEvent::Ready {
36+
data_about_bot, ..
37+
} => {
38+
println!("{} is connected!", data_about_bot.user.name);
39+
},
40+
_ => {},
3241
}
3342
}
34-
35-
async fn ready(&self, _: Context, ready: Ready) {
36-
println!("{} is connected!", ready.user.name);
37-
}
3843
}
3944

4045
#[tokio::main]

examples/e03_struct_utilities/src/main.rs

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,41 @@
11
use serenity::async_trait;
22
use serenity::builder::CreateMessage;
3-
use serenity::model::channel::Message;
4-
use serenity::model::gateway::Ready;
3+
use serenity::gateway::client::FullEvent;
54
use serenity::prelude::*;
65

76
struct Handler;
87

98
#[async_trait]
109
impl EventHandler for Handler {
11-
async fn message(&self, context: Context, msg: Message) {
12-
if msg.content == "!messageme" {
13-
// If the `utils`-feature is enabled, then model structs will have a lot of useful
14-
// methods implemented, to avoid using an often otherwise bulky Context, or even much
15-
// lower-level `rest` method.
16-
//
17-
// In this case, you can direct message a User directly by simply calling a method on
18-
// its instance, with the content of the message.
19-
let builder = CreateMessage::new().content("Hello!");
20-
let dm = msg.author.id.dm(&context.http, builder).await;
10+
async fn dispatch(&self, ctx: &Context, event: &FullEvent) {
11+
match event {
12+
FullEvent::Message {
13+
new_message, ..
14+
} => {
15+
if new_message.content == "!messageme" {
16+
// If the `utils`-feature is enabled, then model structs will have a lot of
17+
// useful methods implemented, to avoid using an often
18+
// otherwise bulky Context, or even much lower-level `rest`
19+
// method.
20+
//
21+
// In this case, you can direct message a User directly by simply calling a
22+
// method on its instance, with the content of the message.
23+
let builder = CreateMessage::new().content("Hello!");
24+
let dm = new_message.author.id.dm(&ctx.http, builder).await;
2125

22-
if let Err(why) = dm {
23-
println!("Error when direct messaging user: {why:?}");
24-
}
25-
}
26-
}
26+
if let Err(why) = dm {
27+
println!("Error when direct messaging user: {why:?}");
28+
}
29+
}
30+
},
2731

28-
async fn ready(&self, _: Context, ready: Ready) {
29-
println!("{} is connected!", ready.user.name);
32+
FullEvent::Ready {
33+
data_about_bot, ..
34+
} => {
35+
println!("{} is connected!", data_about_bot.user.name);
36+
},
37+
_ => {},
38+
}
3039
}
3140
}
3241

examples/e04_message_builder/src/main.rs

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,51 @@
11
use serenity::async_trait;
2-
use serenity::model::channel::Message;
3-
use serenity::model::gateway::Ready;
2+
use serenity::gateway::client::FullEvent;
43
use serenity::prelude::*;
54
use serenity::utils::MessageBuilder;
65

76
struct Handler;
87

98
#[async_trait]
109
impl EventHandler for Handler {
11-
async fn message(&self, context: Context, msg: Message) {
12-
if msg.content == "!ping" {
13-
let channel = match msg.channel(&context).await {
14-
Ok(channel) => channel,
15-
Err(why) => {
16-
println!("Error getting channel: {why:?}");
10+
async fn dispatch(&self, ctx: &Context, event: &FullEvent) {
11+
match event {
12+
FullEvent::Message {
13+
new_message, ..
14+
} => {
15+
if new_message.content == "!ping" {
16+
let channel = match new_message.channel(ctx).await {
17+
Ok(channel) => channel,
18+
Err(why) => {
19+
println!("Error getting channel: {why:?}");
1720

18-
return;
19-
},
20-
};
21+
return;
22+
},
23+
};
2124

22-
// The message builder allows for creating a message by mentioning users dynamically,
23-
// pushing "safe" versions of content (such as bolding normalized content), displaying
24-
// emojis, and more.
25-
let response = MessageBuilder::new()
26-
.push("User ")
27-
.push_bold_safe(msg.author.name.as_str())
28-
.push(" used the 'ping' command in the ")
29-
.mention(&channel)
30-
.push(" channel")
31-
.build();
25+
// The message builder allows for creating a message by mentioning users
26+
// dynamically, pushing "safe" versions of content (such as
27+
// bolding normalized content), displaying emojis, and more.
28+
let response = MessageBuilder::new()
29+
.push("User ")
30+
.push_bold_safe(new_message.author.name.as_str())
31+
.push(" used the 'ping' command in the ")
32+
.mention(&channel)
33+
.push(" channel")
34+
.build();
3235

33-
if let Err(why) = msg.channel_id.say(&context.http, &response).await {
34-
println!("Error sending message: {why:?}");
35-
}
36+
if let Err(why) = new_message.channel_id.say(&ctx.http, &response).await {
37+
println!("Error sending message: {why:?}");
38+
}
39+
}
40+
},
41+
FullEvent::Ready {
42+
data_about_bot, ..
43+
} => {
44+
println!("{} is connected!", data_about_bot.user.name);
45+
},
46+
_ => {},
3647
}
3748
}
38-
39-
async fn ready(&self, _: Context, ready: Ready) {
40-
println!("{} is connected!", ready.user.name);
41-
}
4249
}
4350

4451
#[tokio::main]

0 commit comments

Comments
 (0)