use std::sync::{
Arc, Mutex,
atomic::{AtomicBool, Ordering},
mpsc,
};
use tokio::sync::oneshot;
use crate::{NativeContent, worker};
#[derive(Debug, thiserror::Error, uniffi::Error)]
pub enum NativeError {
#[error("component is closed")]
Closed,
#[error("{detail}")]
Runtime {
detail: String,
},
}
#[derive(Clone, Debug, PartialEq, Eq, uniffi::Record)]
pub struct NativeEvent {
pub name: String,
pub value: String,
}
#[derive(Clone, Copy, uniffi::Enum)]
pub enum TouchPhase {
Down,
Move,
Up,
Cancel,
}
#[derive(uniffi::Record)]
pub struct NativeSlot {
pub id: u64,
pub kind: String,
pub value: String,
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
}
#[derive(uniffi::Record)]
pub struct NativeFrame {
pub width: u32,
pub height: u32,
pub pixels: Vec<u8>,
pub slots: Vec<NativeSlot>,
pub events: Vec<NativeEvent>,
pub next_frame_ms: Option<u64>,
}
#[uniffi::export(callback_interface)]
pub trait FrameListener: Send + Sync {
fn request_frame(&self);
}
pub(crate) enum Command {
Frame(
u32,
u32,
f32,
oneshot::Sender<Result<NativeFrame, NativeError>>,
),
Touch(TouchPhase, f32, f32),
Event(NativeEvent),
NativeEvent(u64, String),
Visible(bool),
Close,
}
#[derive(Default)]
pub(crate) struct Wake(Mutex<Option<Arc<dyn FrameListener>>>);
impl Wake {
pub(crate) fn request(&self) {
let listener = self
.0
.lock()
.ok()
.and_then(|guard| guard.as_ref().map(Arc::clone));
if let Some(listener) = listener {
listener.request_frame();
}
}
}
#[derive(uniffi::Object)]
pub struct NativeSession {
sender: mpsc::Sender<Command>,
closed: AtomicBool,
wake: Arc<Wake>,
}
impl NativeSession {
pub fn new(factory: impl FnOnce() -> NativeContent + Send + 'static) -> Arc<Self> {
let (sender, receiver) = mpsc::channel();
let wake = Arc::new(Wake::default());
let worker_wake = Arc::clone(&wake);
std::thread::spawn(move || worker::run(receiver, factory, worker_wake));
Arc::new(Self {
sender,
closed: AtomicBool::new(false),
wake,
})
}
fn send(&self, command: Command) -> Result<(), NativeError> {
if self.closed.load(Ordering::Acquire) {
return Err(NativeError::Closed);
}
self.sender.send(command).map_err(|_| NativeError::Closed)
}
}
#[uniffi::export]
impl NativeSession {
pub fn set_listener(&self, listener: Box<dyn FrameListener>) -> Result<(), NativeError> {
if self.closed.load(Ordering::Acquire) {
return Err(NativeError::Closed);
}
*self.wake.0.lock().map_err(|_| NativeError::Closed)? = Some(Arc::from(listener));
self.wake.request();
Ok(())
}
pub async fn frame(
&self,
width: u32,
height: u32,
density: f32,
) -> Result<NativeFrame, NativeError> {
let (sender, receiver) = oneshot::channel();
self.send(Command::Frame(width, height, density, sender))?;
receiver.await.map_err(|_| NativeError::Closed)?
}
pub fn send_event(&self, name: String, value: String) -> Result<(), NativeError> {
self.send(Command::Event(NativeEvent { name, value }))?;
self.wake.request();
Ok(())
}
pub fn touch(&self, phase: TouchPhase, x: f32, y: f32) -> Result<(), NativeError> {
self.send(Command::Touch(phase, x, y))?;
self.wake.request();
Ok(())
}
pub fn native_event(&self, id: u64, event: String) -> Result<(), NativeError> {
self.send(Command::NativeEvent(id, event))?;
self.wake.request();
Ok(())
}
pub fn set_visible(&self, visible: bool) -> Result<(), NativeError> {
self.send(Command::Visible(visible))?;
if visible {
self.wake.request();
}
Ok(())
}
pub fn shutdown(&self) {
if !self.closed.swap(true, Ordering::AcqRel) {
if let Ok(mut listener) = self.wake.0.lock() {
*listener = None;
}
let _ = self.sender.send(Command::Close);
}
}
}
impl Drop for NativeSession {
fn drop(&mut self) {
self.shutdown();
}
}