Skip to content

Commit 3b1db0c

Browse files
authored
Merge refactor/async into staging (#63)
refactor: use async_trait where possible
1 parent e9d684f commit 3b1db0c

File tree

9 files changed

+180
-185
lines changed

9 files changed

+180
-185
lines changed

Cargo.lock

Lines changed: 1 addition & 0 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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ repository = "https://github.com/Kitt3120/lum"
1212
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1313

1414
[dependencies]
15+
async-trait = "0.1.83"
1516
dirs = "5.0.1"
1617
downcast-rs = "1.2.0"
1718
fern = { version = "0.6.2", features = ["chrono", "colored", "date-based"] }
@@ -23,4 +24,4 @@ serenity = { version = "0.12.0", default-features=false, features = ["builder",
2324
sqlx = { version = "0.8.0", features = ["runtime-tokio", "any", "postgres", "mysql", "sqlite", "tls-native-tls", "migrate", "macros", "uuid", "chrono", "json"] }
2425
thiserror = "1.0.52"
2526
tokio = { version = "1.35.1", features = ["full"] }
26-
uuid = { version = "1.10.0", features = ["fast-rng", "macro-diagnostics", "v4"] }
27+
uuid = { version = "1.10.0", features = ["fast-rng", "macro-diagnostics", "v4"] }

src/bot.rs

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ use std::{fmt::Display, sync::Arc};
44
use log::error;
55
use tokio::{signal, sync::Mutex};
66

7-
use crate::service::{
8-
types::LifetimedPinnedBoxedFuture, OverallStatus, Service, ServiceManager, ServiceManagerBuilder,
9-
};
7+
use crate::service::{OverallStatus, Service, ServiceManager, ServiceManagerBuilder};
108

119
#[derive(Debug, Clone, Copy)]
1210
pub enum ExitReason {
@@ -68,20 +66,14 @@ impl Bot {
6866
BotBuilder::new(name)
6967
}
7068

71-
//TODO: When Rust allows async trait methods to be object-safe, refactor this to use async instead of returning a future
72-
pub fn start(&mut self) -> LifetimedPinnedBoxedFuture<'_, ()> {
73-
Box::pin(async move {
74-
self.service_manager.start_services().await;
75-
//TODO: Potential for further initialization here, like modules
76-
})
69+
pub async fn start(&mut self) {
70+
self.service_manager.start_services().await;
71+
//TODO: Potential for further initialization here, like modules
7772
}
7873

79-
//TODO: When Rust allows async trait methods to be object-safe, refactor this to use async instead of returning a future
80-
pub fn stop(&mut self) -> LifetimedPinnedBoxedFuture<'_, ()> {
81-
Box::pin(async move {
82-
self.service_manager.stop_services().await;
83-
//TODO: Potential for further deinitialization here, like modules
84-
})
74+
pub async fn stop(&mut self) {
75+
self.service_manager.stop_services().await;
76+
//TODO: Potential for further deinitialization here, like modules
8577
}
8678

8779
pub async fn join(&self) -> ExitReason {

src/service.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
pub mod discord;
2-
// TODO: Used for downcast_rs. Maybe this can be removed when updating the crate.
3-
#[allow(clippy::multiple_bound_locations)]
42
pub mod service; // Will be fixed when lum gets seperated into multiple workspaces
53
pub mod service_manager;
64
pub mod types;
@@ -9,7 +7,7 @@ pub mod watchdog;
97
pub use service::{Service, ServiceInfo};
108
pub use service_manager::{ServiceManager, ServiceManagerBuilder};
119
pub use types::{
12-
BoxedError, BoxedFuture, BoxedFutureResult, OverallStatus, PinnedBoxedFuture, PinnedBoxedFutureResult,
13-
Priority, ShutdownError, StartupError, Status,
10+
BoxedError, LifetimedPinnedBoxedFuture, LifetimedPinnedBoxedFutureResult, OverallStatus,
11+
PinnedBoxedFuture, PinnedBoxedFutureResult, Priority, ShutdownError, StartupError, Status,
1412
};
1513
pub use watchdog::Watchdog;

src/service/discord.rs

Lines changed: 68 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::{types::LifetimedPinnedBoxedFutureResult, Priority, Service, ServiceInfo, ServiceManager};
1+
use super::{BoxedError, Priority, Service, ServiceInfo, ServiceManager};
22
use log::{error, info, warn};
33
use serenity::{
44
all::{GatewayIntents, Ready},
@@ -52,97 +52,97 @@ impl DiscordService {
5252
}
5353
}
5454

55+
//TODO: When Rust allows async trait methods to be object-safe, refactor this to not use async_trait anymore
56+
#[async_trait]
5557
impl Service for DiscordService {
5658
fn info(&self) -> &ServiceInfo {
5759
&self.info
5860
}
5961

60-
fn start(&mut self, _service_manager: Arc<ServiceManager>) -> LifetimedPinnedBoxedFutureResult<'_, ()> {
61-
Box::pin(async move {
62-
let client_ready_notify = Arc::new(Notify::new());
62+
async fn start(&mut self, _service_manager: Arc<ServiceManager>) -> Result<(), BoxedError> {
63+
let client_ready_notify = Arc::new(Notify::new());
6364

64-
let framework = StandardFramework::new();
65-
framework.configure(Configuration::new().prefix("!"));
65+
let framework = StandardFramework::new();
66+
framework.configure(Configuration::new().prefix("!"));
6667

67-
let mut client = Client::builder(self.discord_token.as_str(), GatewayIntents::all())
68-
.framework(framework)
69-
.event_handler(EventHandler::new(
70-
Arc::clone(&self.ready),
71-
Arc::clone(&client_ready_notify),
72-
))
73-
.await?;
68+
let mut client = Client::builder(self.discord_token.as_str(), GatewayIntents::all())
69+
.framework(framework)
70+
.event_handler(EventHandler::new(
71+
Arc::clone(&self.ready),
72+
Arc::clone(&client_ready_notify),
73+
))
74+
.await?;
7475

75-
if self.cache.set(Arc::clone(&client.cache)).is_err() {
76-
error!("Could not set cache OnceLock because it was already set. This should never happen.");
77-
return Err("Could not set cache OnceLock because it was already set.".into());
78-
}
76+
if self.cache.set(Arc::clone(&client.cache)).is_err() {
77+
error!("Could not set cache OnceLock because it was already set. This should never happen.");
78+
return Err("Could not set cache OnceLock because it was already set.".into());
79+
}
7980

80-
if self.data.set(Arc::clone(&client.data)).is_err() {
81-
error!("Could not set data OnceLock because it was already set. This should never happen.");
82-
return Err("Could not set data OnceLock because it was already set.".into());
83-
}
81+
if self.data.set(Arc::clone(&client.data)).is_err() {
82+
error!("Could not set data OnceLock because it was already set. This should never happen.");
83+
return Err("Could not set data OnceLock because it was already set.".into());
84+
}
8485

85-
if self.http.set(Arc::clone(&client.http)).is_err() {
86-
error!("Could not set http OnceLock because it was already set. This should never happen.");
87-
return Err("Could not set http OnceLock because it was already set.".into());
88-
}
86+
if self.http.set(Arc::clone(&client.http)).is_err() {
87+
error!("Could not set http OnceLock because it was already set. This should never happen.");
88+
return Err("Could not set http OnceLock because it was already set.".into());
89+
}
8990

90-
if self.shard_manager.set(Arc::clone(&client.shard_manager)).is_err() {
91-
error!("Could not set shard_manager OnceLock because it was already set. This should never happen.");
92-
return Err("Could not set shard_manager OnceLock because it was already set.".into());
93-
}
91+
if self.shard_manager.set(Arc::clone(&client.shard_manager)).is_err() {
92+
error!(
93+
"Could not set shard_manager OnceLock because it was already set. This should never happen."
94+
);
95+
return Err("Could not set shard_manager OnceLock because it was already set.".into());
96+
}
9497

95-
if let Some(voice_manager) = &client.voice_manager {
96-
if self.voice_manager.set(Arc::clone(voice_manager)).is_err() {
97-
error!("Could not set voice_manager OnceLock because it was already set. This should never happen.");
98-
return Err("Could not set voice_manager OnceLock because it was already set.".into());
99-
}
100-
} else {
101-
warn!("Voice manager is not available");
98+
if let Some(voice_manager) = &client.voice_manager {
99+
if self.voice_manager.set(Arc::clone(voice_manager)).is_err() {
100+
error!("Could not set voice_manager OnceLock because it was already set. This should never happen.");
101+
return Err("Could not set voice_manager OnceLock because it was already set.".into());
102102
}
103+
} else {
104+
warn!("Voice manager is not available");
105+
}
103106

104-
if self.ws_url.set(Arc::clone(&client.ws_url)).is_err() {
105-
error!("Could not set ws_url OnceLock because it was already set. This should never happen.");
106-
return Err("Could not set ws_url OnceLock because it was already set.".into());
107-
}
107+
if self.ws_url.set(Arc::clone(&client.ws_url)).is_err() {
108+
error!("Could not set ws_url OnceLock because it was already set. This should never happen.");
109+
return Err("Could not set ws_url OnceLock because it was already set.".into());
110+
}
108111

109-
let client_handle = spawn(async move { client.start().await });
112+
let client_handle = spawn(async move { client.start().await });
110113

111-
select! {
112-
_ = client_ready_notify.notified() => {},
113-
_ = sleep(Duration::from_secs(2)) => {},
114-
}
114+
select! {
115+
_ = client_ready_notify.notified() => {},
116+
_ = sleep(Duration::from_secs(2)) => {},
117+
}
115118

116-
if client_handle.is_finished() {
117-
client_handle.await??;
118-
return Err("Discord client stopped unexpectedly".into());
119-
}
119+
if client_handle.is_finished() {
120+
client_handle.await??;
121+
return Err("Discord client stopped unexpectedly".into());
122+
}
120123

121-
self.client_handle = Some(client_handle);
122-
Ok(())
123-
})
124+
self.client_handle = Some(client_handle);
125+
Ok(())
124126
}
125127

126-
fn stop(&mut self) -> LifetimedPinnedBoxedFutureResult<'_, ()> {
127-
Box::pin(async move {
128-
if let Some(client_handle) = self.client_handle.take() {
129-
info!("Waiting for Discord client to stop...");
128+
async fn stop(&mut self) -> Result<(), BoxedError> {
129+
if let Some(client_handle) = self.client_handle.take() {
130+
info!("Waiting for Discord client to stop...");
130131

131-
client_handle.abort(); // Should trigger a JoinError in the client_handle, if the task hasn't already ended
132+
client_handle.abort(); // Should trigger a JoinError in the client_handle, if the task hasn't already ended
132133

133-
// If the thread ended WITHOUT a JoinError, the client already stopped unexpectedly
134-
let result = async move {
135-
match client_handle.await {
136-
Ok(result) => result,
137-
Err(_) => Ok(()),
138-
}
134+
// If the thread ended WITHOUT a JoinError, the client already stopped unexpectedly
135+
let result = async move {
136+
match client_handle.await {
137+
Ok(result) => result,
138+
Err(_) => Ok(()),
139139
}
140-
.await;
141-
result?;
142140
}
141+
.await;
142+
result?;
143+
}
143144

144-
Ok(())
145-
})
145+
Ok(())
146146
}
147147
}
148148

@@ -157,6 +157,7 @@ impl EventHandler {
157157
}
158158
}
159159

160+
//TODO: When Rust allows async trait methods to be object-safe, refactor this to not use async_trait anymore
160161
#[async_trait]
161162
impl client::EventHandler for EventHandler {
162163
async fn ready(&self, _ctx: Context, data_about_bot: Ready) {

src/service/service.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ use std::{
44
sync::Arc,
55
};
66

7+
use async_trait::async_trait;
78
use downcast_rs::{impl_downcast, DowncastSync};
89

910
use crate::event::Observable;
1011

1112
use super::{
1213
service_manager::ServiceManager,
13-
types::{LifetimedPinnedBoxedFuture, LifetimedPinnedBoxedFutureResult, Priority, Status},
14+
types::{Priority, Status},
15+
BoxedError, LifetimedPinnedBoxedFutureResult,
1416
};
1517

1618
#[derive(Debug)]
@@ -58,17 +60,18 @@ impl Hash for ServiceInfo {
5860
self.id.hash(state);
5961
}
6062
}
61-
//TODO: When Rust allows async trait methods to be object-safe, refactor this to use async instead of returning a PinnedBoxedFutureResult
63+
//TODO: When Rust allows async trait methods to be object-safe, refactor this to not use async_trait anymore
64+
#[async_trait]
6265
pub trait Service: DowncastSync {
6366
fn info(&self) -> &ServiceInfo;
64-
fn start(&mut self, service_manager: Arc<ServiceManager>) -> LifetimedPinnedBoxedFutureResult<'_, ()>;
65-
fn stop(&mut self) -> LifetimedPinnedBoxedFutureResult<'_, ()>;
67+
async fn start(&mut self, service_manager: Arc<ServiceManager>) -> Result<(), BoxedError>;
68+
async fn stop(&mut self) -> Result<(), BoxedError>;
6669
fn task<'a>(&self) -> Option<LifetimedPinnedBoxedFutureResult<'a, ()>> {
6770
None
6871
}
6972

70-
fn is_available(&self) -> LifetimedPinnedBoxedFuture<'_, bool> {
71-
Box::pin(async move { matches!(self.info().status.get().await, Status::Started) })
73+
async fn is_available(&self) -> bool {
74+
matches!(self.info().status.get().await, Status::Started)
7275
}
7376
}
7477

0 commit comments

Comments
 (0)