Skip to content

Commit

Permalink
Refactor service framework (#25)
Browse files Browse the repository at this point in the history
* Service framework improvements

 - Way better handling of mutable/immutable attributes
 - Less Mutexes
 - Better handling of passing references through the service framework's API
 - Reimplement get_service accepting a TypeId as a generic parameter for easier usage
 - Reimplement status_map and status_tree as a result of the above adaptations, resulting in way simpler versions

* More service framework improvements

 - Replace all Mutexes with RwLock
 - Remove status_map method of ServiceManager
 - Services vector in ServiceManager now wraps the Service trait objects in a RwLock to potentially make them available mutably through the public API of ServiceManager

* Implement get_service<T> method

 - Add downcast-rs crate
 - Implement get_service<T> method of ServiceManager

Had to use unsafe Rust for this. Tried it with safe Rust for 3 days and couldn't do it. With unsafe Rust, it's very easy. It's also still kinda safe, as the crash case is checked and prevented before going into the unsafe block.

* Finish refactor of service framework

 - ServiceManager now holds an Arc to itself
 - Self-Arc is now passed to services when initializing them, so they can access other services and copy Arcs to those for themselves
 - Implement SetLock<T> struct which is a wrapper around Option<T> for lazy-initialization
 - ServiceManagerBuilder handles the creation and injection of the Self-Arc of ServiceManager. That's why the build() method is now async and the From trait had to be removed. The From trait cannot be implemented async.
 - To keep everything consistent, the From trait has also been removed from the BotBuilder and the build() method becase async.

* Adapt Discord service

 - Adapt Discord service to new service framework and SetLock type
  • Loading branch information
Kitt3120 committed Jan 7, 2024
1 parent f059ed0 commit 7bad3ad
Show file tree
Hide file tree
Showing 7 changed files with 335 additions and 211 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ repository = "https://github.com/Kitt3120/lum"

[dependencies]
dirs = "5.0.1"
downcast-rs = "1.2.0"
fern = { version = "0.6.2", features = ["chrono", "colored", "date-based"] }
humantime = "2.1.0"
log = { version = "0.4.20", features = ["serde"] }
Expand Down
34 changes: 16 additions & 18 deletions src/bot.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use std::sync::Arc;

use tokio::sync::RwLock;

use crate::service::{PinnedBoxedFuture, Service, ServiceManager, ServiceManagerBuilder};

pub struct BotBuilder {
Expand All @@ -13,28 +17,31 @@ impl BotBuilder {
}
}

pub fn with_service(mut self, service: Box<dyn Service>) -> Self {
self.service_manager = self.service_manager.with_service(service); // The ServiceManagerBuilder itself will warn when adding a service multiple times
pub async fn with_service(mut self, service: Arc<RwLock<dyn Service>>) -> Self {
self.service_manager = self.service_manager.with_service(service).await; // The ServiceManagerBuilder itself will warn when adding a service multiple times

self
}

pub fn with_services(mut self, services: Vec<Box<dyn Service>>) -> Self {
pub async fn with_services(mut self, services: Vec<Arc<RwLock<dyn Service>>>) -> Self {
for service in services {
self.service_manager = self.service_manager.with_service(service);
self.service_manager = self.service_manager.with_service(service).await;
}

self
}

pub fn build(self) -> Bot {
Bot::from(self)
pub async fn build(self) -> Bot {
Bot {
name: self.name,
service_manager: self.service_manager.build().await,
}
}
}

pub struct Bot {
pub name: String,
pub service_manager: ServiceManager,
pub service_manager: Arc<RwLock<ServiceManager>>,
}

impl Bot {
Expand All @@ -45,25 +52,16 @@ impl Bot {
//TODO: When Rust allows async trait methods to be object-safe, refactor this to use async instead of returning a future
pub fn start(&mut self) -> PinnedBoxedFuture<'_, ()> {
Box::pin(async move {
self.service_manager.start_services().await;
self.service_manager.write().await.start_services().await;
//TODO: Potential for further initialization here, like modules
})
}

//TODO: When Rust allows async trait methods to be object-safe, refactor this to use async instead of returning a future
pub fn stop(&mut self) -> PinnedBoxedFuture<'_, ()> {
Box::pin(async move {
self.service_manager.stop_services().await;
self.service_manager.write().await.stop_services().await;
//TODO: Potential for further deinitialization here, like modules
})
}
}

impl From<BotBuilder> for Bot {
fn from(builder: BotBuilder) -> Self {
Self {
name: builder.name,
service_manager: builder.service_manager.build(),
}
}
}
7 changes: 5 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod bot;
pub mod config;
pub mod log;
pub mod service;
pub mod setlock;

pub fn is_debug() -> bool {
cfg!(debug_assertions)
Expand Down Expand Up @@ -35,15 +36,17 @@ pub async fn run(mut bot: Bot) {
}
};

if bot.service_manager.overall_status().await != OverallStatus::Healthy {
let status_tree = bot.service_manager.status_tree().await;
let service_manager = bot.service_manager.read().await;
if service_manager.overall_status().await != OverallStatus::Healthy {
let status_tree = service_manager.status_tree().await;

error!("{} is not healthy! Some essential services did not start up successfully. Please check the logs.\nService status tree:\n{}\n{} will exit.",
bot.name,
status_tree,
bot.name);
return;
}
drop(service_manager);

info!("{} is alive", bot.name,);

Expand Down
11 changes: 8 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use std::sync::Arc;

use ::log::{error, warn};
use lum::{
bot::Bot,
config::{Config, ConfigHandler},
log,
service::{discord::DiscordService, Service},
};
use tokio::sync::RwLock;

const BOT_NAME: &str = "Lum";

Expand All @@ -31,7 +34,9 @@ async fn main() {

let bot = Bot::builder(BOT_NAME)
.with_services(initialize_services(&config))
.build();
.await
.build()
.await;

lum::run(bot).await;
}
Expand All @@ -45,12 +50,12 @@ fn setup_logger() {
}
}

fn initialize_services(config: &Config) -> Vec<Box<dyn Service>> {
fn initialize_services(config: &Config) -> Vec<Arc<RwLock<dyn Service>>> {
//TODO: Add services
//...

let discord_service =
DiscordService::new(config.discord_token.as_str(), config.discord_timeout);

vec![Box::new(discord_service)]
vec![Arc::new(RwLock::new(discord_service))]
}
Loading

0 comments on commit 7bad3ad

Please sign in to comment.