navcore_ffi/lib.rs
1//! The UniFFI adapter: nav-core's boundary to clients written in other
2//! languages.
3//!
4//! Everything UniFFI-shaped lives here, in the outermost layer, rather than
5//! as derives bolted onto `fix` or `nav-math`'s own types -- the workspace
6//! `Cargo.toml`'s own architecture comment is "nav-math must not know
7//! Signal K exists", and a `#[derive(uniffi::Record)]` on `fix::VesselState` would
8//! be exactly that. This crate converts, it does not redefine: the types
9//! it wraps stay exactly what they are elsewhere.
10//!
11//! Deliberately narrow for now: a client has nothing to draw without a
12//! source of vessel state, and [`FfiSimulator`] is the only one that
13//! exists yet, so it is the only one exposed. A real one (Signal K) joins
14//! later without this shape needing to change.
15//!
16//! `route` is the other side of that same growth: a client that can
17//! draw a vessel also needs somewhere to send it, and `route_find` is
18//! nav-core's answer to "where."
19
20mod route;
21
22use std::sync::Mutex;
23
24use fix::{Simulator, VesselState};
25use nav_math::Position;
26
27pub use route::{FfiChartStore, FfiRouteError};
28
29uniffi::setup_scaffolding!();
30
31/// A geographic position, crossing the FFI boundary.
32#[derive(uniffi::Record)]
33pub struct FfiPosition {
34 /// Degrees north of the equator.
35 pub lat_deg: f64,
36 /// Degrees east of Greenwich.
37 pub lon_deg: f64,
38}
39
40impl From<Position> for FfiPosition {
41 fn from(position: Position) -> Self {
42 Self {
43 lat_deg: position.lat_deg,
44 lon_deg: position.lon_deg,
45 }
46 }
47}
48
49impl From<FfiPosition> for Position {
50 fn from(position: FfiPosition) -> Self {
51 Self::new(position.lat_deg, position.lon_deg)
52 }
53}
54
55/// What a client draws about own ship at one instant.
56///
57/// `predictor_end`/`heading_line_end` are computed once here, via
58/// [`VesselState`]'s own methods, rather than left for a client to derive
59/// -- the whole reason this computation lives in `nav-math` rather than here.
60/// This shape is not incidental: it is exactly what a chart bridge --
61/// each client's own module for it, on whichever platform -- already
62/// expects to send onward.
63#[derive(uniffi::Record)]
64pub struct FfiVesselState {
65 /// Where the vessel is.
66 pub position: FfiPosition,
67 /// The far end of the course predictor.
68 pub predictor_end: FfiPosition,
69 /// The far end of the heading line, when the vessel has a compass.
70 pub heading_line_end: Option<FfiPosition>,
71 /// Course over ground, degrees true.
72 pub cog_deg: f64,
73 /// Speed over ground, knots.
74 pub sog_kn: f64,
75 /// Heading, degrees true, when the vessel has a compass.
76 pub heading_deg: Option<f64>,
77}
78
79impl From<VesselState> for FfiVesselState {
80 fn from(state: VesselState) -> Self {
81 Self {
82 position: state.position.into(),
83 predictor_end: state.predictor_end().into(),
84 heading_line_end: state.heading_line_end().map(Into::into),
85 cog_deg: state.cog_deg,
86 sog_kn: state.sog_kn,
87 heading_deg: state.heading_deg,
88 }
89 }
90}
91
92/// A vessel with nothing real behind it, for a client to have something to
93/// draw before any real source exists -- see [`Simulator`]'s own doc.
94///
95/// Wraps the state in a mutex rather than requiring `&mut self`: a UniFFI
96/// object is handed to the foreign side as one shared reference, not
97/// something a caller on that side can hold `mut`.
98#[derive(uniffi::Object)]
99pub struct FfiSimulator {
100 inner: Mutex<Simulator>,
101}
102
103#[uniffi::export]
104impl FfiSimulator {
105 /// A vessel under way in the Gulf of Trieste, where this project's
106 /// charts have coverage.
107 #[uniffi::constructor]
108 #[must_use]
109 pub fn new() -> Self {
110 Self {
111 inner: Mutex::new(Simulator::new()),
112 }
113 }
114
115 /// Advances the vessel by `dt_seconds` and returns the new state.
116 #[must_use]
117 pub fn tick(&self, dt_seconds: f64) -> FfiVesselState {
118 let mut simulator = self.inner.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
119 simulator.tick(std::time::Duration::from_secs_f64(dt_seconds)).into()
120 }
121}
122
123impl Default for FfiSimulator {
124 fn default() -> Self {
125 Self::new()
126 }
127}