use std::collections::HashMap;
use std::os::raw::c_void;
use yog_sync::{Arc, RwLock};
use yog_abi::{YogAddress, YogBytes, YogHandlerFn, YogReply, YogStatus, status};
use yog_hot_reload::{CallGuard, LoadedModule, ModuleGeneration};
#[derive(Clone)]
pub(crate) enum Handler {
Native {
owner: Option<LoadedModule>,
ud: usize,
f: YogHandlerFn,
},
Java(i32),
}
#[derive(Clone)]
struct Subscriber {
priority: i32,
handler: Handler,
}
pub(crate) type JavaRunner = fn(handler: i32, payload: &[u8], reply: &YogReply) -> YogStatus;
#[derive(Default)]
pub(crate) struct Router {
providers: RwLock<Arc<HashMap<YogAddress, Handler>>>,
subscribers: RwLock<HashMap<YogAddress, Arc<Vec<Subscriber>>>>,
}
impl Router {
pub(crate) fn resolve(&self, name: &str) -> YogAddress {
yog_event::resolve(name)
}
pub(crate) fn provide(&self, address: YogAddress, handler: Handler) -> bool {
let mut providers = self.providers.write();
if providers.contains_key(&address) {
return false;
}
Arc::make_mut(&mut providers).insert(address, handler);
true
}
pub(crate) fn provided(&self, address: YogAddress) -> bool {
self.providers.read().contains_key(&address)
}
pub(crate) fn on(&self, event: YogAddress, priority: i32, handler: Handler) {
let mut subs = self.subscribers.write();
let list = Arc::make_mut(subs.entry(event).or_default());
let at = list
.iter()
.position(|s| s.priority < priority)
.unwrap_or(list.len());
list.insert(at, Subscriber { priority, handler });
}
pub(crate) fn heard(&self, event: YogAddress) -> bool {
self.subscribers
.read()
.get(&event)
.is_some_and(|list| !list.is_empty())
}
pub(crate) fn listened_keys(&self, address: &str) -> Vec<String> {
let branch = yog_event::keyed(address, "");
let mut keys: Vec<String> = self
.subscribers
.read()
.iter()
.filter(|(_, list)| !list.is_empty())
.filter_map(|(at, _)| {
yog_data::atom::local::name(*at)?
.strip_prefix(branch.as_str())
.map(str::to_owned)
})
.collect();
keys.sort();
keys
}
pub(crate) fn call(
&self,
to: YogAddress,
payload: &[u8],
reply: &YogReply,
java: JavaRunner,
) -> YogStatus {
let providers = Arc::clone(&self.providers.read());
match providers.get(&to) {
Some(handler) => run(handler, payload, reply, java),
None => status::NO_HANDLER,
}
}
pub(crate) fn emit(
&self,
event: YogAddress,
payload: &[u8],
reply: &YogReply,
java: JavaRunner,
) -> YogStatus {
let Some(subs) = self.subscribers.read().get(&event).cloned() else {
return status::OK;
};
let (mut current, mut next) = (Vec::new(), Vec::new());
let mut changed = false;
let mut st = status::OK;
let mut entered: Option<(ModuleGeneration, CallGuard<'_>)> = None;
for s in subs.iter() {
let payload_now = if changed { current.as_slice() } else { payload };
next.clear();
let out = YogReply::to_vec(&mut next);
st = match &s.handler {
Handler::Java(id) => java(*id, payload_now, &out),
Handler::Native { owner: None, ud, f } => unsafe {
f(*ud as *mut c_void, YogBytes::new(payload_now), &out)
},
Handler::Native {
owner: Some(m),
ud,
f,
} => {
if entered.as_ref().is_none_or(|(g, _)| *g != m.generation()) {
entered = None;
match m.enter() {
Some(guard) => entered = Some((m.generation(), guard)),
None => continue,
}
}
unsafe { f(*ud as *mut c_void, YogBytes::new(payload_now), &out) }
}
};
if !next.is_empty() {
std::mem::swap(&mut current, &mut next);
changed = true;
}
if st == status::CANCELLED {
break;
}
}
drop(entered);
if changed {
unsafe { YogReply::send(reply, ¤t) };
}
if st == status::CANCELLED {
st
} else {
status::OK
}
}
pub(crate) fn call_vec(
&self,
to: YogAddress,
payload: &[u8],
java: JavaRunner,
) -> (YogStatus, Vec<u8>) {
let mut out = Vec::new();
let st = self.call(to, payload, &YogReply::to_vec(&mut out), java);
(st, out)
}
#[cfg(test)]
pub(crate) fn emit_vec(
&self,
event: YogAddress,
payload: &[u8],
java: JavaRunner,
) -> (YogStatus, Vec<u8>) {
let mut out = Vec::new();
let st = self.emit(event, payload, &YogReply::to_vec(&mut out), java);
if out.is_empty() {
out = payload.to_vec();
}
(st, out)
}
pub(crate) fn purge(&self, generation: ModuleGeneration) {
let owned_by = |h: &Handler| matches!(h, Handler::Native { owner: Some(m), .. } if m.generation() == generation);
Arc::make_mut(&mut self.providers.write()).retain(|_, h| !owned_by(h));
for list in self.subscribers.write().values_mut() {
Arc::make_mut(list).retain(|s| !owned_by(&s.handler));
}
}
}
fn run(handler: &Handler, payload: &[u8], reply: &YogReply, java: JavaRunner) -> YogStatus {
match handler {
Handler::Java(id) => java(*id, payload, reply),
Handler::Native { owner, ud, f } => {
let _guard = match owner {
Some(m) => match m.enter() {
Some(g) => Some(g),
None => return status::NO_HANDLER,
},
None => None,
};
unsafe { f(*ud as *mut c_void, YogBytes::new(payload), reply) }
}
}
}