|
| 1 | +// Copyright © SixtyFPS GmbH <[email protected]> |
| 2 | +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 |
| 3 | + |
| 4 | +use std::rc::Rc; |
| 5 | + |
| 6 | +use pyo3::prelude::*; |
| 7 | +use pyo3_stub_gen::{derive::gen_stub_pyclass, derive::gen_stub_pymethods}; |
| 8 | + |
| 9 | +#[cfg(unix)] |
| 10 | +struct PyFdWrapper(std::os::fd::RawFd); |
| 11 | + |
| 12 | +#[cfg(unix)] |
| 13 | +impl std::os::fd::AsFd for PyFdWrapper { |
| 14 | + fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> { |
| 15 | + unsafe { std::os::fd::BorrowedFd::borrow_raw(self.0) } |
| 16 | + } |
| 17 | +} |
| 18 | + |
| 19 | +#[cfg(windows)] |
| 20 | +struct PyFdWrapper(#[cfg(windows)] std::os::windows::io::RawSocket); |
| 21 | + |
| 22 | +#[cfg(windows)] |
| 23 | +impl std::os::windows::io::AsSocket for PyFdWrapper { |
| 24 | + fn as_socket(&self) -> std::os::windows::io::BorrowedSocket<'_> { |
| 25 | + unsafe { std::os::windows::io::BorrowedSocket::borrow_raw(self.0) } |
| 26 | + } |
| 27 | +} |
| 28 | + |
| 29 | +struct AdapterInner { |
| 30 | + adapter: smol::Async<PyFdWrapper>, |
| 31 | + readable_callback: Option<Py<PyAny>>, |
| 32 | + writable_callback: Option<Py<PyAny>>, |
| 33 | +} |
| 34 | + |
| 35 | +#[gen_stub_pyclass] |
| 36 | +#[pyclass(unsendable)] |
| 37 | +pub struct AsyncAdapter { |
| 38 | + inner: Option<Rc<AdapterInner>>, |
| 39 | + task: Option<slint_interpreter::JoinHandle<()>>, |
| 40 | +} |
| 41 | + |
| 42 | +#[gen_stub_pymethods] |
| 43 | +#[pymethods] |
| 44 | +impl AsyncAdapter { |
| 45 | + #[new] |
| 46 | + fn py_new(fd: i32) -> Self { |
| 47 | + #[cfg(windows)] |
| 48 | + let fd = u64::try_from(fd).unwrap(); |
| 49 | + AsyncAdapter { |
| 50 | + inner: Some(Rc::new(AdapterInner { |
| 51 | + adapter: smol::Async::new(PyFdWrapper(fd)).unwrap(), |
| 52 | + readable_callback: Default::default(), |
| 53 | + writable_callback: Default::default(), |
| 54 | + })), |
| 55 | + task: None, |
| 56 | + } |
| 57 | + } |
| 58 | + |
| 59 | + fn wait_for_readable(&mut self, callback: Py<PyAny>) { |
| 60 | + self.restart_after_mut_inner_access(|inner| { |
| 61 | + inner.readable_callback.replace(callback); |
| 62 | + }); |
| 63 | + } |
| 64 | + |
| 65 | + fn wait_for_writable(&mut self, callback: Py<PyAny>) { |
| 66 | + self.restart_after_mut_inner_access(|inner| { |
| 67 | + inner.writable_callback.replace(callback); |
| 68 | + }); |
| 69 | + } |
| 70 | +} |
| 71 | + |
| 72 | +impl AsyncAdapter { |
| 73 | + fn restart_after_mut_inner_access(&mut self, callback: impl FnOnce(&mut AdapterInner)) { |
| 74 | + if let Some(task) = self.task.take() { |
| 75 | + task.abort(); |
| 76 | + } |
| 77 | + |
| 78 | + // This detaches and basically makes any existing future that might get woke up fail when |
| 79 | + // trying to upgrade the weak. |
| 80 | + let mut inner = Rc::into_inner(self.inner.take().unwrap()).unwrap(); |
| 81 | + |
| 82 | + callback(&mut inner); |
| 83 | + |
| 84 | + let inner = Rc::new(inner); |
| 85 | + let inner_weak = Rc::downgrade(&inner); |
| 86 | + self.inner = Some(inner); |
| 87 | + self.task = Some( |
| 88 | + slint_interpreter::spawn_local(std::future::poll_fn(move |cx| loop { |
| 89 | + let Some(inner) = inner_weak.upgrade() else { |
| 90 | + return std::task::Poll::Ready(()); |
| 91 | + }; |
| 92 | + |
| 93 | + let readable_poll_status: Option<std::task::Poll<Py<PyAny>>> = |
| 94 | + inner.readable_callback.as_ref().map(|callback| { |
| 95 | + if inner.adapter.poll_readable(cx).is_ready() { |
| 96 | + std::task::Poll::Ready(Python::attach(|py| callback.clone_ref(py))) |
| 97 | + } else { |
| 98 | + std::task::Poll::Pending |
| 99 | + } |
| 100 | + }); |
| 101 | + |
| 102 | + let writable_poll_status: Option<std::task::Poll<Py<PyAny>>> = |
| 103 | + inner.writable_callback.as_ref().map(|callback| { |
| 104 | + if inner.adapter.poll_writable(cx).is_ready() { |
| 105 | + std::task::Poll::Ready(Python::attach(|py| callback.clone_ref(py))) |
| 106 | + } else { |
| 107 | + std::task::Poll::Pending |
| 108 | + } |
| 109 | + }); |
| 110 | + |
| 111 | + let fd = inner.adapter.get_ref().0; |
| 112 | + |
| 113 | + drop(inner); |
| 114 | + |
| 115 | + if let Some(std::task::Poll::Ready(callback)) = &readable_poll_status { |
| 116 | + Python::attach(|py| { |
| 117 | + callback.call1(py, (fd,)).expect( |
| 118 | + "unexpected failure running python async readable adapter callback", |
| 119 | + ); |
| 120 | + }); |
| 121 | + } |
| 122 | + |
| 123 | + if let Some(std::task::Poll::Ready(callback)) = &writable_poll_status { |
| 124 | + Python::attach(|py| { |
| 125 | + callback.call1(py, (fd,)).expect( |
| 126 | + "unexpected failure running python async writable adapter callback", |
| 127 | + ); |
| 128 | + }); |
| 129 | + } |
| 130 | + |
| 131 | + match &readable_poll_status { |
| 132 | + Some(std::task::Poll::Ready(..)) => continue, // poll again and then probably return in the next iteration |
| 133 | + Some(std::task::Poll::Pending) => return std::task::Poll::Pending, // waker registered, come back later |
| 134 | + None => {} // Nothing to poll |
| 135 | + } |
| 136 | + |
| 137 | + match &writable_poll_status { |
| 138 | + Some(std::task::Poll::Ready(..)) => continue, // poll again and then probably return in the next iteration |
| 139 | + Some(std::task::Poll::Pending) => return std::task::Poll::Pending, // waker registered, come back later |
| 140 | + None => {} // Nothing to poll |
| 141 | + } |
| 142 | + |
| 143 | + return std::task::Poll::Ready(()); |
| 144 | + })) |
| 145 | + .unwrap(), |
| 146 | + ); |
| 147 | + } |
| 148 | +} |
0 commit comments