Skip to main content

maolan_plugin_protocol/
events.rs

1use std::io;
2use std::time::Duration;
3
4#[cfg(unix)]
5use std::os::unix::io::RawFd;
6
7/// Lightweight cross-process event signalling.
8///
9/// Unix: two independent pipes provide bidirectional wake-up.
10/// Windows: two named auto-reset events provide bidirectional wake-up.
11#[cfg(unix)]
12pub struct EventPair {
13    daw_to_host: [RawFd; 2],
14    host_to_daw: [RawFd; 2],
15}
16
17#[cfg(windows)]
18pub struct EventPair {
19    daw_to_host: *mut std::ffi::c_void,
20    host_to_daw: *mut std::ffi::c_void,
21    daw_to_host_name: String,
22    host_to_daw_name: String,
23}
24
25unsafe impl Send for EventPair {}
26unsafe impl Sync for EventPair {}
27
28#[cfg(unix)]
29impl EventPair {
30    /// Create two pipes. Returns `Err` if `pipe(2)` fails.
31    pub fn new() -> io::Result<Self> {
32        let mut daw_to_host = [0; 2];
33        let mut host_to_daw = [0; 2];
34        if unsafe { libc::pipe(daw_to_host.as_mut_ptr()) } != 0 {
35            return Err(io::Error::last_os_error());
36        }
37        if unsafe { libc::pipe(host_to_daw.as_mut_ptr()) } != 0 {
38            unsafe {
39                libc::close(daw_to_host[0]);
40                libc::close(daw_to_host[1]);
41            }
42            return Err(io::Error::last_os_error());
43        }
44        Ok(Self {
45            daw_to_host,
46            host_to_daw,
47        })
48    }
49
50    /// # Safety
51    /// `daw_to_host_read` and `host_to_daw_write` must be valid,
52    /// already-open file descriptors inherited from the parent process.
53    pub unsafe fn from_fds(daw_to_host_read: RawFd, host_to_daw_write: RawFd) -> Self {
54        let mut pair = Self {
55            daw_to_host: [daw_to_host_read, -1],
56            host_to_daw: [-1, host_to_daw_write],
57        };
58        pair.close_host_unused();
59        pair
60    }
61
62    pub fn daw_write_fd(&self) -> RawFd {
63        self.daw_to_host[1]
64    }
65
66    pub fn daw_read_fd(&self) -> RawFd {
67        self.host_to_daw[0]
68    }
69
70    pub fn host_read_fd(&self) -> RawFd {
71        self.daw_to_host[0]
72    }
73
74    pub fn host_write_fd(&self) -> RawFd {
75        self.host_to_daw[1]
76    }
77
78    /// DAW wakes the host.
79    pub fn signal_host(&self) -> io::Result<()> {
80        write_byte(self.daw_to_host[1])
81    }
82
83    /// DAW waits for host completion (with timeout).
84    pub fn wait_host(&self, timeout: Duration) -> io::Result<()> {
85        read_byte(self.host_to_daw[0], timeout)
86    }
87
88    /// Host waits for DAW wake (with timeout).
89    pub fn wait_daw(&self, timeout: Duration) -> io::Result<()> {
90        read_byte(self.daw_to_host[0], timeout)
91    }
92
93    /// Host signals completion to DAW.
94    pub fn signal_daw(&self) -> io::Result<()> {
95        write_byte(self.host_to_daw[1])
96    }
97
98    /// Close the file descriptors that the DAW side does not need.
99    pub fn close_daw_unused(&mut self) {
100        unsafe {
101            libc::close(self.daw_to_host[0]);
102            libc::close(self.host_to_daw[1]);
103        }
104        self.daw_to_host[0] = -1;
105        self.host_to_daw[1] = -1;
106    }
107
108    /// Close the file descriptors that the host side does not need.
109    pub fn close_host_unused(&mut self) {
110        unsafe {
111            libc::close(self.daw_to_host[1]);
112            libc::close(self.host_to_daw[0]);
113        }
114        self.daw_to_host[1] = -1;
115        self.host_to_daw[0] = -1;
116    }
117}
118
119#[cfg(unix)]
120impl Drop for EventPair {
121    fn drop(&mut self) {
122        unsafe {
123            libc::close(self.daw_to_host[0]);
124            libc::close(self.daw_to_host[1]);
125            libc::close(self.host_to_daw[0]);
126            libc::close(self.host_to_daw[1]);
127        }
128    }
129}
130
131#[cfg(unix)]
132fn write_byte(fd: RawFd) -> io::Result<()> {
133    let buf = [1u8];
134    let n = unsafe { libc::write(fd, buf.as_ptr().cast(), 1) };
135    if n < 0 {
136        Err(io::Error::last_os_error())
137    } else {
138        Ok(())
139    }
140}
141
142#[cfg(unix)]
143fn read_byte(fd: RawFd, timeout: Duration) -> io::Result<()> {
144    let mut pfd = libc::pollfd {
145        fd,
146        events: libc::POLLIN,
147        revents: 0,
148    };
149    let ms = timeout.as_millis().clamp(0, i32::MAX as u128) as i32;
150    let rc = unsafe { libc::poll(&mut pfd, 1, ms) };
151    if rc < 0 {
152        return Err(io::Error::last_os_error());
153    }
154    if rc == 0 {
155        return Err(io::Error::new(io::ErrorKind::TimedOut, "poll timeout"));
156    }
157    let mut buf = [0u8; 1];
158    let n = unsafe { libc::read(fd, buf.as_mut_ptr().cast(), 1) };
159    if n < 0 {
160        Err(io::Error::last_os_error())
161    } else {
162        Ok(())
163    }
164}
165
166#[cfg(windows)]
167impl EventPair {
168    /// Create two named auto-reset events.
169    pub fn new() -> io::Result<Self> {
170        use windows_sys::Win32::Foundation::GetLastError;
171        use windows_sys::Win32::System::Threading::CreateEventW;
172
173        let pid = std::process::id();
174        let nonce = std::time::SystemTime::now()
175            .duration_since(std::time::UNIX_EPOCH)
176            .unwrap_or_default()
177            .as_nanos();
178        let daw_to_host_name = format!("Local\\maolan-d2h-{}-{}", pid, nonce);
179        let host_to_daw_name = format!("Local\\maolan-h2d-{}-{}", pid, nonce);
180
181        let d2h_wide: Vec<u16> = daw_to_host_name
182            .encode_utf16()
183            .chain(std::iter::once(0))
184            .collect();
185        let h2d_wide: Vec<u16> = host_to_daw_name
186            .encode_utf16()
187            .chain(std::iter::once(0))
188            .collect();
189
190        let daw_to_host = unsafe { CreateEventW(std::ptr::null_mut(), 0, 0, d2h_wide.as_ptr()) };
191        if daw_to_host.is_null() {
192            return Err(io::Error::new(
193                io::ErrorKind::Other,
194                format!("CreateEventW failed: {}", unsafe { GetLastError() }),
195            ));
196        }
197        let host_to_daw = unsafe { CreateEventW(std::ptr::null_mut(), 0, 0, h2d_wide.as_ptr()) };
198        if host_to_daw.is_null() {
199            unsafe { windows_sys::Win32::Foundation::CloseHandle(daw_to_host) };
200            return Err(io::Error::new(
201                io::ErrorKind::Other,
202                format!("CreateEventW failed: {}", unsafe { GetLastError() }),
203            ));
204        }
205
206        Ok(Self {
207            daw_to_host,
208            host_to_daw,
209            daw_to_host_name,
210            host_to_daw_name,
211        })
212    }
213
214    /// Reconstruct from event names (host process).
215    pub fn from_names(daw_to_host_name: &str, host_to_daw_name: &str) -> io::Result<Self> {
216        use windows_sys::Win32::Foundation::GetLastError;
217        use windows_sys::Win32::System::Threading::OpenEventW;
218
219        let d2h_wide: Vec<u16> = daw_to_host_name
220            .encode_utf16()
221            .chain(std::iter::once(0))
222            .collect();
223        let h2d_wide: Vec<u16> = host_to_daw_name
224            .encode_utf16()
225            .chain(std::iter::once(0))
226            .collect();
227
228        let daw_to_host = unsafe {
229            OpenEventW(
230                windows_sys::Win32::System::Threading::EVENT_ALL_ACCESS,
231                0,
232                d2h_wide.as_ptr(),
233            )
234        };
235        if daw_to_host.is_null() {
236            return Err(io::Error::new(
237                io::ErrorKind::Other,
238                format!("OpenEventW failed: {}", unsafe { GetLastError() }),
239            ));
240        }
241        let host_to_daw = unsafe {
242            OpenEventW(
243                windows_sys::Win32::System::Threading::EVENT_ALL_ACCESS,
244                0,
245                h2d_wide.as_ptr(),
246            )
247        };
248        if host_to_daw.is_null() {
249            unsafe { windows_sys::Win32::Foundation::CloseHandle(daw_to_host) };
250            return Err(io::Error::new(
251                io::ErrorKind::Other,
252                format!("OpenEventW failed: {}", unsafe { GetLastError() }),
253            ));
254        }
255
256        Ok(Self {
257            daw_to_host,
258            host_to_daw,
259            daw_to_host_name: daw_to_host_name.to_string(),
260            host_to_daw_name: host_to_daw_name.to_string(),
261        })
262    }
263
264    pub fn daw_to_host_name(&self) -> &str {
265        &self.daw_to_host_name
266    }
267
268    pub fn host_to_daw_name(&self) -> &str {
269        &self.host_to_daw_name
270    }
271
272    /// DAW wakes the host.
273    pub fn signal_host(&self) -> io::Result<()> {
274        use windows_sys::Win32::Foundation::GetLastError;
275        use windows_sys::Win32::System::Threading::SetEvent;
276        if unsafe { SetEvent(self.daw_to_host) } == 0 {
277            return Err(io::Error::new(
278                io::ErrorKind::Other,
279                format!("SetEvent failed: {}", unsafe { GetLastError() }),
280            ));
281        }
282        Ok(())
283    }
284
285    /// DAW waits for host completion (with timeout).
286    pub fn wait_host(&self, timeout: Duration) -> io::Result<()> {
287        self.wait_object(self.host_to_daw, timeout)
288    }
289
290    /// Host waits for DAW wake (with timeout).
291    pub fn wait_daw(&self, timeout: Duration) -> io::Result<()> {
292        self.wait_object(self.daw_to_host, timeout)
293    }
294
295    /// Host waits for DAW wake (with timeout) while pumping the Win32 message queue.
296    pub fn wait_daw_with_message_pump(&self, timeout: Duration) -> io::Result<()> {
297        use windows_sys::Win32::Foundation::{GetLastError, WAIT_OBJECT_0, WAIT_TIMEOUT};
298        use windows_sys::Win32::UI::WindowsAndMessaging::{
299            DispatchMessageW, MSG, MsgWaitForMultipleObjects, PM_REMOVE, PeekMessageW, QS_ALLINPUT,
300            TranslateMessage,
301        };
302
303        let start = std::time::Instant::now();
304        let handles = [self.daw_to_host];
305        let ms_total = timeout.as_millis().clamp(0, u32::MAX as u128) as u32;
306
307        loop {
308            let elapsed = start.elapsed().as_millis().clamp(0, u32::MAX as u128) as u32;
309            let remaining = ms_total.saturating_sub(elapsed);
310
311            let rc = unsafe {
312                MsgWaitForMultipleObjects(1, handles.as_ptr(), 0, remaining, QS_ALLINPUT)
313            };
314
315            if rc == WAIT_OBJECT_0 {
316                return Ok(());
317            } else if rc == WAIT_OBJECT_0 + 1 {
318                unsafe {
319                    let mut msg: MSG = std::mem::zeroed();
320                    while PeekMessageW(&mut msg, std::ptr::null_mut(), 0, 0, PM_REMOVE) != 0 {
321                        TranslateMessage(&msg);
322                        DispatchMessageW(&msg);
323                    }
324                }
325            } else if rc == WAIT_TIMEOUT {
326                return Err(io::Error::new(
327                    io::ErrorKind::TimedOut,
328                    "MsgWaitForMultipleObjects timeout",
329                ));
330            } else {
331                return Err(io::Error::new(
332                    io::ErrorKind::Other,
333                    format!("MsgWaitForMultipleObjects failed: {}", unsafe {
334                        GetLastError()
335                    }),
336                ));
337            }
338        }
339    }
340
341    /// Host signals completion to DAW.
342    pub fn signal_daw(&self) -> io::Result<()> {
343        use windows_sys::Win32::Foundation::GetLastError;
344        use windows_sys::Win32::System::Threading::SetEvent;
345        if unsafe { SetEvent(self.host_to_daw) } == 0 {
346            return Err(io::Error::new(
347                io::ErrorKind::Other,
348                format!("SetEvent failed: {}", unsafe { GetLastError() }),
349            ));
350        }
351        Ok(())
352    }
353
354    /// No-op on Windows (events are opened by name).
355    pub fn close_daw_unused(&mut self) {}
356
357    /// No-op on Windows (events are opened by name).
358    pub fn close_host_unused(&mut self) {}
359
360    fn wait_object(&self, handle: *mut std::ffi::c_void, timeout: Duration) -> io::Result<()> {
361        use windows_sys::Win32::Foundation::{GetLastError, WAIT_OBJECT_0, WAIT_TIMEOUT};
362        use windows_sys::Win32::System::Threading::WaitForSingleObject;
363
364        let ms = timeout.as_millis().clamp(0, u32::MAX as u128) as u32;
365        let rc = unsafe { WaitForSingleObject(handle, ms) };
366        if rc == WAIT_OBJECT_0 {
367            Ok(())
368        } else if rc == WAIT_TIMEOUT {
369            Err(io::Error::new(
370                io::ErrorKind::TimedOut,
371                "WaitForSingleObject timeout",
372            ))
373        } else {
374            Err(io::Error::new(
375                io::ErrorKind::Other,
376                format!("WaitForSingleObject failed: {}", unsafe { GetLastError() }),
377            ))
378        }
379    }
380}
381
382#[cfg(windows)]
383impl Drop for EventPair {
384    fn drop(&mut self) {
385        use windows_sys::Win32::Foundation::CloseHandle;
386        if !self.daw_to_host.is_null() {
387            unsafe { CloseHandle(self.daw_to_host) };
388        }
389        if !self.host_to_daw.is_null() {
390            unsafe { CloseHandle(self.host_to_daw) };
391        }
392    }
393}