use std::ffi::c_void;
use std::panic::{catch_unwind, AssertUnwindSafe};
use crate::context::Context;
use crate::kernel::{register_with_language, Contract, KernelLanguage, KernelVTable};
use crate::packet::Packet;
use crate::status::{code, Error, Result};
use crate::timestamp::Timestamp;
pub trait Kernel: Default + 'static {
fn get_contract(_c: &mut KernelContract) {}
fn open(&mut self, _cc: &mut KernelCtx) -> Result<()> {
Ok(())
}
fn process(&mut self, cc: &mut KernelCtx) -> Result<()>;
fn close(&mut self, _cc: &mut KernelCtx) -> Result<()> {
Ok(())
}
}
pub struct KernelCtx<'a> {
inner: &'a mut Context,
}
impl KernelCtx<'_> {
pub fn num_inputs(&self) -> usize {
self.inner.in_ports.len()
}
pub fn num_outputs(&self) -> usize {
self.inner.out_ports.len()
}
pub fn input(&self, idx: usize) -> Option<&Packet> {
self.inner.input(idx)
}
pub fn input_count(&self, idx: usize) -> usize {
self.inner.input_count(idx)
}
pub fn input_at(&self, idx: usize, k: usize) -> Option<&Packet> {
self.inner.input_at(idx, k)
}
pub fn take_input(&mut self, idx: usize) -> Packet {
self.inner.take_input(idx)
}
pub fn input_timestamp(&self) -> Timestamp {
self.inner.input_ts
}
pub fn input_is_done(&self, idx: usize) -> bool {
self.inner.inputs_done.get(idx).copied().unwrap_or(false)
}
pub fn emit(&mut self, out_idx: usize, pkt: Packet) -> Result<()> {
self.inner.emit(out_idx, pkt)
}
pub fn forward(&mut self, in_idx: usize, out_idx: usize) -> Result<()> {
self.inner.forward(in_idx, out_idx)
}
pub fn set_next_bound(&mut self, out_idx: usize, bound: Timestamp) {
self.inner.set_next_bound(out_idx, bound)
}
pub fn source_done(&mut self) {
self.inner.source_done = true;
}
pub fn source_yield(&mut self, delay: std::time::Duration) {
self.inner.source_yield = Some(delay);
}
pub fn set_error(&mut self, msg: &str) {
self.inner.set_error(msg);
}
pub fn fail(&self, msg: &str) -> Error {
Error::Kernel(msg.to_string())
}
pub fn log(&self, level: i32, msg: &str) {
self.inner.log(level, msg);
}
pub fn log_enabled(&self) -> bool {
crate::runtime::log_enabled()
}
pub fn counter_add(&self, name: &str, delta: i64) {
self.inner.shared.counter_add(name, delta);
}
pub fn has_option(&self, key: &str) -> bool {
self.inner.options.has(key)
}
pub fn option_i64(&self, key: &str, def: i64) -> i64 {
self.inner.options.i64(key).unwrap_or(def)
}
pub fn option_f64(&self, key: &str, def: f64) -> f64 {
self.inner.options.f64(key).unwrap_or(def)
}
pub fn option_bool(&self, key: &str, def: bool) -> bool {
self.inner.options.bool(key).unwrap_or(def)
}
pub fn option_str<'s>(&'s self, key: &str, def: &'s str) -> &'s str {
self.inner.options.str(key).unwrap_or(def)
}
pub fn has_side_packet(&self, name: &str) -> bool {
self.inner.side_packets.contains_key(name)
}
pub fn side_packet(&self, name: &str) -> Option<&Packet> {
self.inner.side_packets.get(name)
}
}
pub struct KernelContract<'a> {
inner: &'a mut Contract,
}
impl KernelContract<'_> {
pub fn num_inputs(&self) -> usize {
self.inner.input_types.len()
}
pub fn num_outputs(&self) -> usize {
self.inner.output_types.len()
}
pub fn input_type(&mut self, i: usize, type_id: u64) {
if let Some(t) = self.inner.input_types.get_mut(i) {
*t = type_id;
} else {
self.inner.record_error(format!(
"input port index {i} is out of range (num_inputs={})",
self.inner.input_types.len()
));
}
}
pub fn output_type(&mut self, i: usize, type_id: u64) {
if let Some(t) = self.inner.output_types.get_mut(i) {
*t = type_id;
} else {
self.inner.record_error(format!(
"output port index {i} is out of range (num_outputs={})",
self.inner.output_types.len()
));
}
}
pub fn input_any(&mut self, i: usize) {
self.input_type(i, 0);
}
pub fn output_any(&mut self, i: usize) {
self.output_type(i, 0);
}
pub fn require_side_packet(&mut self, name: &str) {
if name.is_empty() {
self.inner
.record_error("required side packet name must not be empty");
} else {
self.inner.required_side_packets.push(name.to_string());
}
}
}
unsafe extern "C" fn tramp_create<T: Kernel>(_factory: *mut c_void) -> *mut c_void {
match catch_unwind(|| Box::into_raw(Box::<T>::default()) as *mut c_void) {
Ok(p) => p,
Err(_) => std::ptr::null_mut(), }
}
unsafe extern "C" fn tramp_destroy<T: Kernel>(self_: *mut c_void) {
if !self_.is_null() {
drop(Box::from_raw(self_ as *mut T));
}
}
unsafe extern "C" fn tramp_get_contract<T: Kernel>(_self: *mut c_void, c: *mut c_void) {
if c.is_null() {
return;
}
let result = catch_unwind(AssertUnwindSafe(|| {
let mut kc = KernelContract {
inner: &mut *(c as *mut Contract),
};
T::get_contract(&mut kc);
}));
if let Err(payload) = result {
let message = payload
.downcast_ref::<&str>()
.map(|message| (*message).to_string())
.or_else(|| payload.downcast_ref::<String>().cloned())
.unwrap_or_else(|| "Rust panic with non-string payload".to_string());
(*(c as *mut Contract)).record_error(format!("Rust panic: {message}"));
}
}
unsafe fn run_phase<T: Kernel>(
self_: *mut c_void,
ctx: *mut c_void,
f: impl FnOnce(&mut T, &mut KernelCtx) -> Result<()>,
) -> i32 {
if self_.is_null() || ctx.is_null() {
return code::KERNEL; }
let r = catch_unwind(AssertUnwindSafe(|| {
let k = &mut *(self_ as *mut T);
let mut cc = KernelCtx {
inner: &mut *(ctx as *mut Context),
};
match f(k, &mut cc) {
Ok(()) => code::OK,
Err(e) => {
cc.inner.set_error(&e.to_string());
e.code()
}
}
}));
match r {
Ok(rc) => rc,
Err(_) => {
(*(ctx as *mut Context)).set_error("kernel panicked");
code::PANIC
}
}
}
unsafe extern "C" fn tramp_open<T: Kernel>(self_: *mut c_void, ctx: *mut c_void) -> i32 {
run_phase::<T>(self_, ctx, |k, cc| k.open(cc))
}
unsafe extern "C" fn tramp_process<T: Kernel>(self_: *mut c_void, ctx: *mut c_void) -> i32 {
run_phase::<T>(self_, ctx, |k, cc| k.process(cc))
}
unsafe extern "C" fn tramp_close<T: Kernel>(self_: *mut c_void, ctx: *mut c_void) -> i32 {
run_phase::<T>(self_, ctx, |k, cc| k.close(cc))
}
pub fn register_kernel<T: Kernel>(name: &str) -> Result<()> {
let vt = KernelVTable {
create: Some(tramp_create::<T>),
get_contract: Some(tramp_get_contract::<T>),
open: Some(tramp_open::<T>),
process: Some(tramp_process::<T>),
close: Some(tramp_close::<T>),
destroy: Some(tramp_destroy::<T>),
};
register_with_language(name, vt, std::ptr::null_mut(), KernelLanguage::Rust)
}