use std::{collections::HashMap, error::Error as StdError, sync::Arc};
use rosu_render::{
model::{RenderDone, RenderProgress, RenderSkinOption, Verification},
websocket::event::RawEvent,
OrdrClient, OrdrWebsocket, WebsocketError,
};
use tokio::{
sync::{mpsc, oneshot, RwLock},
task::JoinHandle,
};
struct OrdrSenders {
done: mpsc::Sender<RenderDone>,
progress: mpsc::Sender<RenderProgress>,
}
pub struct OrdrReceivers {
pub done: mpsc::Receiver<RenderDone>,
pub progress: mpsc::Receiver<RenderProgress>,
}
type Senders = Arc<RwLock<HashMap<u32, OrdrSenders>>>;
pub struct Ordr {
client: OrdrClient,
senders: Senders,
shutdown_tx: oneshot::Sender<()>,
websocket_handle: JoinHandle<()>,
}
impl Ordr {
pub async fn subscribe_render_id(&self, render_id: u32) -> OrdrReceivers {
let (done_tx, done_rx) = mpsc::channel(1);
let (progress_tx, progress_rx) = mpsc::channel(1);
let senders = OrdrSenders {
done: done_tx,
progress: progress_tx,
};
let receivers = OrdrReceivers {
done: done_rx,
progress: progress_rx,
};
self.senders.write().await.insert(render_id, senders);
receivers
}
pub async fn unsubscribe_render_id(&self, render_id: u32) {
self.senders.write().await.remove(&render_id);
}
pub async fn new() -> Result<Self, WebsocketError> {
let websocket = OrdrWebsocket::connect().await?;
#[cfg(debug_assertions)]
let verification = Verification::DevModeSuccess;
#[cfg(not(debug_assertions))]
let verification = Verification::Key("my_verification_key".into());
let client = OrdrClient::builder().verification(verification).build();
let senders = Senders::default();
let (shutdown_tx, shutdown_rx) = oneshot::channel();
let websocket_handle = tokio::spawn(Self::handle_websocket_events(
websocket,
Arc::clone(&senders),
shutdown_rx,
));
Ok(Self {
client,
senders,
shutdown_tx,
websocket_handle,
})
}
async fn handle_websocket_events(
mut websocket: OrdrWebsocket,
senders: Senders,
mut shutdown_rx: oneshot::Receiver<()>,
) {
loop {
let event_result = tokio::select! {
event_result = websocket.next_event() => event_result,
_ = &mut shutdown_rx => match websocket.disconnect().await {
Ok(_) => return,
Err(err) => {
println!("Failed to disconnect websocket gracefully: {err}");
return
}
},
};
match event_result {
Ok(event) => match event {
RawEvent::RenderDone(event) => {
let guard = senders.read().await;
if let Some(senders) = guard.get(&event.render_id) {
match event.deserialize() {
Ok(done) => {
let _ = senders.done.send(done).await;
}
Err(err) => println!("Failed to deserialize RenderDone: {err}"),
}
}
}
RawEvent::RenderProgress(event) => {
let guard = senders.read().await;
if let Some(senders) = guard.get(&event.render_id) {
match event.deserialize() {
Ok(progress) => {
let _ = senders.progress.send(progress).await;
}
Err(err) => {
println!("Failed to deserialize RenderProgress: {err}")
}
}
}
}
_ => {} },
Err(err) => println!("Websocket error: {err}"),
}
}
}
pub async fn disconnect(self) {
if self.shutdown_tx.send(()).is_ok() {
self.websocket_handle
.await
.expect("websocket worker panicked");
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn StdError>> {
let ordr = Ordr::new().await?;
let replay = tokio::fs::read("./assets/2283307549.osr").await?;
let skin = RenderSkinOption::default();
let commission = ordr
.client
.render_with_replay_file(&replay, "rosu-render-example", &skin)
.await?;
let mut receivers = ordr.subscribe_render_id(commission.render_id).await;
loop {
tokio::select! {
event = receivers.progress.recv() => {
let event = event.expect("sender was dropped");
println!("Progress: {}", event.progress);
}
event = receivers.done.recv() => {
let event = event.expect("sender was dropped");
println!("Done: URL={}", event.video_url);
break;
}
}
}
ordr.unsubscribe_render_id(commission.render_id).await;
ordr.disconnect().await;
Ok(())
}