1use std::sync::{
2 Arc, Mutex,
3 atomic::{AtomicBool, Ordering},
4 mpsc,
5};
6
7use tokio::sync::oneshot;
8
9use crate::{NativeContent, worker};
10
11#[derive(Debug, thiserror::Error, uniffi::Error)]
13pub enum NativeError {
14 #[error("component is closed")]
16 Closed,
17 #[error("{detail}")]
19 Runtime {
20 detail: String,
22 },
23}
24
25#[derive(Clone, Debug, PartialEq, Eq, uniffi::Record)]
27pub struct NativeEvent {
28 pub name: String,
30 pub value: String,
32}
33
34#[derive(Clone, Copy, uniffi::Enum)]
36pub enum TouchPhase {
37 Down,
39 Move,
41 Up,
43 Cancel,
45}
46
47#[derive(uniffi::Record)]
49pub struct NativeSlot {
50 pub id: u64,
52 pub kind: String,
54 pub value: String,
56 pub x: f32,
58 pub y: f32,
60 pub width: f32,
62 pub height: f32,
64}
65
66#[derive(uniffi::Record)]
68pub struct NativeFrame {
69 pub width: u32,
71 pub height: u32,
73 pub pixels: Vec<u8>,
75 pub slots: Vec<NativeSlot>,
77 pub events: Vec<NativeEvent>,
79 pub next_frame_ms: Option<u64>,
81}
82
83#[uniffi::export(callback_interface)]
85pub trait FrameListener: Send + Sync {
86 fn request_frame(&self);
88}
89
90pub(crate) enum Command {
91 Frame(
92 u32,
93 u32,
94 f32,
95 oneshot::Sender<Result<NativeFrame, NativeError>>,
96 ),
97 Touch(TouchPhase, f32, f32),
98 Event(NativeEvent),
99 NativeEvent(u64, String),
100 Visible(bool),
101 Close,
102}
103
104#[derive(Default)]
105pub(crate) struct Wake(Mutex<Option<Arc<dyn FrameListener>>>);
106
107impl Wake {
108 pub(crate) fn request(&self) {
109 let listener = self
110 .0
111 .lock()
112 .ok()
113 .and_then(|guard| guard.as_ref().map(Arc::clone));
114 if let Some(listener) = listener {
115 listener.request_frame();
116 }
117 }
118}
119
120#[derive(uniffi::Object)]
125pub struct NativeSession {
126 sender: mpsc::Sender<Command>,
127 closed: AtomicBool,
128 wake: Arc<Wake>,
129}
130
131impl NativeSession {
132 pub fn new(factory: impl FnOnce() -> NativeContent + Send + 'static) -> Arc<Self> {
135 let (sender, receiver) = mpsc::channel();
136 let wake = Arc::new(Wake::default());
137 let worker_wake = Arc::clone(&wake);
138 std::thread::spawn(move || worker::run(receiver, factory, worker_wake));
139 Arc::new(Self {
140 sender,
141 closed: AtomicBool::new(false),
142 wake,
143 })
144 }
145
146 fn send(&self, command: Command) -> Result<(), NativeError> {
147 if self.closed.load(Ordering::Acquire) {
148 return Err(NativeError::Closed);
149 }
150 self.sender.send(command).map_err(|_| NativeError::Closed)
151 }
152}
153
154#[uniffi::export]
155impl NativeSession {
156 pub fn set_listener(&self, listener: Box<dyn FrameListener>) -> Result<(), NativeError> {
158 if self.closed.load(Ordering::Acquire) {
159 return Err(NativeError::Closed);
160 }
161 *self.wake.0.lock().map_err(|_| NativeError::Closed)? = Some(Arc::from(listener));
162 self.wake.request();
163 Ok(())
164 }
165
166 pub async fn frame(
168 &self,
169 width: u32,
170 height: u32,
171 density: f32,
172 ) -> Result<NativeFrame, NativeError> {
173 let (sender, receiver) = oneshot::channel();
174 self.send(Command::Frame(width, height, density, sender))?;
175 receiver.await.map_err(|_| NativeError::Closed)?
176 }
177
178 pub fn send_event(&self, name: String, value: String) -> Result<(), NativeError> {
180 self.send(Command::Event(NativeEvent { name, value }))?;
181 self.wake.request();
182 Ok(())
183 }
184
185 pub fn touch(&self, phase: TouchPhase, x: f32, y: f32) -> Result<(), NativeError> {
187 self.send(Command::Touch(phase, x, y))?;
188 self.wake.request();
189 Ok(())
190 }
191
192 pub fn native_event(&self, id: u64, event: String) -> Result<(), NativeError> {
194 self.send(Command::NativeEvent(id, event))?;
195 self.wake.request();
196 Ok(())
197 }
198
199 pub fn set_visible(&self, visible: bool) -> Result<(), NativeError> {
201 self.send(Command::Visible(visible))?;
202 if visible {
203 self.wake.request();
204 }
205 Ok(())
206 }
207
208 pub fn shutdown(&self) {
210 if !self.closed.swap(true, Ordering::AcqRel) {
211 if let Ok(mut listener) = self.wake.0.lock() {
212 *listener = None;
213 }
214 let _ = self.sender.send(Command::Close);
215 }
216 }
217}
218
219impl Drop for NativeSession {
220 fn drop(&mut self) {
221 self.shutdown();
222 }
223}