use std::sync::Arc;
use crate::document::DocumentEvent;
pub trait DocumentScheduler: Send + Sync + 'static {
fn post(&self, doc_id: usize, event: DocumentEvent);
fn is_alive(&self) -> bool {
true
}
}
pub struct MpscDocumentScheduler {
tx: std::sync::mpsc::Sender<DocumentEvent>,
}
impl MpscDocumentScheduler {
pub fn new(tx: std::sync::mpsc::Sender<DocumentEvent>) -> Self {
Self { tx }
}
pub fn with_channel() -> (Self, std::sync::mpsc::Receiver<DocumentEvent>) {
let (tx, rx) = std::sync::mpsc::channel();
(Self::new(tx), rx)
}
}
impl DocumentScheduler for MpscDocumentScheduler {
fn post(&self, _doc_id: usize, event: DocumentEvent) {
let _ = self.tx.send(event);
}
fn is_alive(&self) -> bool {
self.tx.send(DocumentEvent::Ping).is_ok()
}
}
#[cfg(feature = "tokio")]
pub struct TokioDocumentScheduler {
tx: tokio::sync::mpsc::UnboundedSender<(usize, DocumentEvent)>,
}
#[cfg(feature = "tokio")]
impl TokioDocumentScheduler {
pub fn new(tx: tokio::sync::mpsc::UnboundedSender<(usize, DocumentEvent)>) -> Self {
Self { tx }
}
pub fn with_channel() -> (
Self,
tokio::sync::mpsc::UnboundedReceiver<(usize, DocumentEvent)>,
) {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
(Self::new(tx), rx)
}
}
#[cfg(feature = "tokio")]
impl DocumentScheduler for TokioDocumentScheduler {
fn post(&self, doc_id: usize, event: DocumentEvent) {
let _ = self.tx.send((doc_id, event));
}
fn is_alive(&self) -> bool {
!self.tx.is_closed()
}
}
pub struct CallbackDocumentScheduler {
callback: Box<dyn Fn(usize, DocumentEvent) + Send + Sync>,
}
impl CallbackDocumentScheduler {
pub fn new<F>(callback: F) -> Self
where
F: Fn(usize, DocumentEvent) + Send + Sync + 'static,
{
Self {
callback: Box::new(callback),
}
}
}
impl DocumentScheduler for CallbackDocumentScheduler {
fn post(&self, doc_id: usize, event: DocumentEvent) {
(self.callback)(doc_id, event);
}
}
pub struct NoopDocumentScheduler;
impl DocumentScheduler for NoopDocumentScheduler {
fn post(&self, _doc_id: usize, _event: DocumentEvent) {
}
}
pub type SharedDocumentScheduler = Arc<dyn DocumentScheduler>;
pub fn create_default_scheduler() -> (
SharedDocumentScheduler,
std::sync::mpsc::Receiver<DocumentEvent>,
) {
let (scheduler, rx) = MpscDocumentScheduler::with_channel();
(Arc::new(scheduler), rx)
}
pub fn create_callback_scheduler<F>(callback: F) -> SharedDocumentScheduler
where
F: Fn(usize, DocumentEvent) + Send + Sync + 'static,
{
Arc::new(CallbackDocumentScheduler::new(callback))
}
pub fn create_noop_scheduler() -> SharedDocumentScheduler {
Arc::new(NoopDocumentScheduler)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::net::{Resource, ResourceLoadResponse};
fn make_test_event() -> DocumentEvent {
DocumentEvent::ResourceLoad(ResourceLoadResponse {
request_id: 0,
node_id: Some(42),
resolved_url: Some("test.png".to_string()),
result: Ok(Resource::None),
})
}
#[test]
fn test_mpsc_scheduler() {
let (scheduler, rx) = MpscDocumentScheduler::with_channel();
scheduler.post(1, make_test_event());
let received = rx.recv().unwrap();
match received {
DocumentEvent::ResourceLoad(recv) => {
assert_eq!(recv.node_id, Some(42));
assert_eq!(recv.resolved_url.as_deref(), Some("test.png"));
}
DocumentEvent::Ping => panic!("Expected ResourceLoad, got Ping"),
}
}
#[test]
fn test_callback_scheduler() {
let received = std::sync::Arc::new(std::sync::Mutex::new(None));
let received_clone = received.clone();
let scheduler = CallbackDocumentScheduler::new(move |doc_id, event| {
*received_clone.lock().unwrap_or_else(|e| e.into_inner()) = Some((doc_id, event));
});
scheduler.post(123, make_test_event());
let result = received.lock().unwrap_or_else(|e| e.into_inner()).take();
assert!(result.is_some());
let (doc_id, _) = result.unwrap();
assert_eq!(doc_id, 123);
}
#[test]
fn test_noop_scheduler() {
let scheduler = NoopDocumentScheduler;
scheduler.post(1, make_test_event());
}
#[test]
fn test_shared_scheduler() {
let (scheduler, rx) = create_default_scheduler();
scheduler.post(1, make_test_event());
let received = rx.recv();
assert!(received.is_ok());
}
}