use std::sync::mpsc;
use std::sync::{Arc, Mutex};
use tobari::magnetic::TiltedDipole;
use super::sync_bindings::orts::plugin::host_env;
use super::sync_bindings::orts::plugin::tick_io;
use super::sync_bindings::orts::plugin::types as wit;
impl wit::Host for HostState {}
pub(super) enum GuestResponse {
Command(Option<wit::Command>),
Done(Result<(), String>),
}
pub struct HostState {
pub label: String,
field: TiltedDipole,
wasi: wasmtime_wasi::WasiCtx,
table: wasmtime_wasi::ResourceTable,
input_rx: mpsc::Receiver<wit::TickInput>,
output_tx: mpsc::SyncSender<GuestResponse>,
pending_cmd: Option<wit::Command>,
is_first_wait: bool,
#[allow(dead_code)] current_mode: Arc<Mutex<Option<String>>>,
}
impl HostState {
pub(super) fn new(
label: impl Into<String>,
input_rx: mpsc::Receiver<wit::TickInput>,
output_tx: mpsc::SyncSender<GuestResponse>,
current_mode: Arc<Mutex<Option<String>>>,
) -> Self {
Self {
label: label.into(),
field: TiltedDipole::earth(),
wasi: wasmtime_wasi::WasiCtxBuilder::new().build(),
table: wasmtime_wasi::ResourceTable::new(),
input_rx,
output_tx,
pending_cmd: None,
is_first_wait: true,
current_mode,
}
}
}
impl wasmtime_wasi::WasiView for HostState {
fn ctx(&mut self) -> wasmtime_wasi::WasiCtxView<'_> {
wasmtime_wasi::WasiCtxView {
ctx: &mut self.wasi,
table: &mut self.table,
}
}
}
impl wasmtime::component::HasData for HostState {
type Data<'a> = &'a mut HostState;
}
impl host_env::Host for HostState {
fn log(&mut self, level: host_env::LogLevel, message: String) {
match level {
host_env::LogLevel::Trace => log::trace!("[wasm:{}] {}", self.label, message),
host_env::LogLevel::Debug => log::debug!("[wasm:{}] {}", self.label, message),
host_env::LogLevel::Info => log::info!("[wasm:{}] {}", self.label, message),
host_env::LogLevel::Warn => log::warn!("[wasm:{}] {}", self.label, message),
host_env::LogLevel::Error => log::error!("[wasm:{}] {}", self.label, message),
}
}
fn magnetic_field_eci(&mut self, position_eci_km: wit::Vec3, epoch: wit::Epoch) -> wit::Vec3 {
let pos = arika::frame::Vec3::<arika::frame::SimpleEci>::new(
position_eci_km.x,
position_eci_km.y,
position_eci_km.z,
);
let epoch = arika::epoch::Epoch::from_jd(epoch.julian_date);
let b = crate::magnetic::field_eci(&self.field, &pos, &epoch);
wit::Vec3 {
x: b.x(),
y: b.y(),
z: b.z(),
}
}
}
impl tick_io::Host for HostState {
fn wait_tick(&mut self) -> Option<wit::TickInput> {
if !self.is_first_wait {
let cmd = self.pending_cmd.take();
let _ = self.output_tx.send(GuestResponse::Command(cmd));
} else {
self.is_first_wait = false;
}
self.input_rx.recv().ok()
}
fn send_command(&mut self, cmd: wit::Command) {
self.pending_cmd = Some(cmd);
}
}
#[cfg(test)]
mod tests {
use super::host_env::Host as _;
use super::*;
fn make_state() -> HostState {
let (_, input_rx) = mpsc::channel();
let (output_tx, _) = mpsc::sync_channel(1);
let current_mode = Arc::new(Mutex::new(None));
HostState::new("test", input_rx, output_tx, current_mode)
}
#[test]
fn magnetic_field_returns_finite_nonzero_for_leo() {
let mut state = make_state();
let pos = wit::Vec3 {
x: 7000.0,
y: 0.0,
z: 0.0,
};
let epoch = wit::Epoch {
julian_date: 2451545.0,
};
let b = state.magnetic_field_eci(pos, epoch);
assert!(b.x.is_finite());
assert!(b.y.is_finite());
assert!(b.z.is_finite());
let magnitude = (b.x * b.x + b.y * b.y + b.z * b.z).sqrt();
assert!(
magnitude > 1e-5 && magnitude < 1e-4,
"expected LEO-range magnetic field (~20-60 µT), got {magnitude:.3e} T"
);
}
}