use std::{boxed::Box as Box_, future::Future, mem::transmute, panic, ptr};
use glib::{
prelude::*,
signal::{connect_raw, SignalHandlerId},
translate::*,
value::ValueType,
};
use futures_channel::oneshot;
use crate::{AsyncResult, Cancellable};
glib::wrapper! {
#[doc(alias = "GTask")]
pub struct LocalTask<V: ValueType>(Object<ffi::GTask, ffi::GTaskClass>) @implements AsyncResult;
match fn {
type_ => || ffi::g_task_get_type(),
}
}
glib::wrapper! {
#[doc(alias = "GTask")]
pub struct Task<V: ValueType + Send>(Object<ffi::GTask, ffi::GTaskClass>) @implements AsyncResult;
match fn {
type_ => || ffi::g_task_get_type(),
}
}
macro_rules! task_impl {
($name:ident $(, @bound: $bound:tt)? $(, @safety: $safety:tt)?) => {
impl <V: Into<glib::Value> + ValueType $(+ $bound)?> $name<V> {
#[doc(alias = "g_task_new")]
#[allow(unused_unsafe)]
pub unsafe fn new<S, P, Q>(
source_object: Option<&S>,
cancellable: Option<&P>,
callback: Q,
) -> Self
where
S: IsA<glib::Object> $(+ $bound)?,
P: IsA<Cancellable>,
Q: FnOnce($name<V>, Option<&S>) $(+ $bound)? + 'static,
{
let callback_data = Box_::new(callback);
unsafe extern "C" fn trampoline<
S: IsA<glib::Object> $(+ $bound)?,
V: ValueType $(+ $bound)?,
Q: FnOnce($name<V>, Option<&S>) $(+ $bound)? + 'static,
>(
source_object: *mut glib::gobject_ffi::GObject,
res: *mut ffi::GAsyncResult,
user_data: glib::ffi::gpointer,
) {
let callback: Box_<Q> = Box::from_raw(user_data as *mut _);
let task = AsyncResult::from_glib_none(res)
.downcast::<$name<V>>()
.unwrap();
let source_object = Option::<glib::Object>::from_glib_borrow(source_object);
callback(
task,
source_object.as_ref().as_ref().map(|s| s.unsafe_cast_ref()),
);
}
let callback = trampoline::<S, V, Q>;
unsafe {
from_glib_full(ffi::g_task_new(
source_object.map(|p| p.as_ref()).to_glib_none().0,
cancellable.map(|p| p.as_ref()).to_glib_none().0,
Some(callback),
Box_::into_raw(callback_data) as *mut _,
))
}
}
#[doc(alias = "g_task_get_cancellable")]
#[doc(alias = "get_cancellable")]
pub fn cancellable(&self) -> Option<Cancellable> {
unsafe { from_glib_none(ffi::g_task_get_cancellable(self.to_glib_none().0)) }
}
#[doc(alias = "g_task_get_check_cancellable")]
#[doc(alias = "get_check_cancellable")]
pub fn is_check_cancellable(&self) -> bool {
unsafe { from_glib(ffi::g_task_get_check_cancellable(self.to_glib_none().0)) }
}
#[doc(alias = "g_task_set_check_cancellable")]
pub fn set_check_cancellable(&self, check_cancellable: bool) {
unsafe {
ffi::g_task_set_check_cancellable(self.to_glib_none().0, check_cancellable.into_glib());
}
}
#[cfg(any(feature = "v2_60", feature = "dox"))]
#[cfg_attr(feature = "dox", doc(cfg(feature = "v2_60")))]
#[doc(alias = "g_task_set_name")]
pub fn set_name(&self, name: Option<&str>) {
unsafe {
ffi::g_task_set_name(self.to_glib_none().0, name.to_glib_none().0);
}
}
#[doc(alias = "g_task_set_return_on_cancel")]
pub fn set_return_on_cancel(&self, return_on_cancel: bool) -> bool {
unsafe {
from_glib(ffi::g_task_set_return_on_cancel(
self.to_glib_none().0,
return_on_cancel.into_glib(),
))
}
}
#[doc(alias = "g_task_is_valid")]
pub fn is_valid(
result: &impl IsA<AsyncResult>,
source_object: Option<&impl IsA<glib::Object>>,
) -> bool {
unsafe {
from_glib(ffi::g_task_is_valid(
result.as_ref().to_glib_none().0,
source_object.map(|p| p.as_ref()).to_glib_none().0,
))
}
}
#[doc(alias = "get_priority")]
#[doc(alias = "g_task_get_priority")]
pub fn priority(&self) -> glib::source::Priority {
unsafe { FromGlib::from_glib(ffi::g_task_get_priority(self.to_glib_none().0)) }
}
#[doc(alias = "g_task_set_priority")]
pub fn set_priority(&self, priority: glib::source::Priority) {
unsafe {
ffi::g_task_set_priority(self.to_glib_none().0, priority.into_glib());
}
}
#[doc(alias = "g_task_get_completed")]
#[doc(alias = "get_completed")]
pub fn is_completed(&self) -> bool {
unsafe { from_glib(ffi::g_task_get_completed(self.to_glib_none().0)) }
}
#[doc(alias = "g_task_get_context")]
#[doc(alias = "get_context")]
pub fn context(&self) -> glib::MainContext {
unsafe { from_glib_none(ffi::g_task_get_context(self.to_glib_none().0)) }
}
#[cfg(any(feature = "v2_60", feature = "dox"))]
#[cfg_attr(feature = "dox", doc(cfg(feature = "v2_60")))]
#[doc(alias = "g_task_get_name")]
#[doc(alias = "get_name")]
pub fn name(&self) -> Option<glib::GString> {
unsafe { from_glib_none(ffi::g_task_get_name(self.to_glib_none().0)) }
}
#[doc(alias = "g_task_get_return_on_cancel")]
#[doc(alias = "get_return_on_cancel")]
pub fn is_return_on_cancel(&self) -> bool {
unsafe { from_glib(ffi::g_task_get_return_on_cancel(self.to_glib_none().0)) }
}
#[doc(alias = "g_task_had_error")]
pub fn had_error(&self) -> bool {
unsafe { from_glib(ffi::g_task_had_error(self.to_glib_none().0)) }
}
#[doc(alias = "completed")]
pub fn connect_completed_notify<F>(&self, f: F) -> SignalHandlerId
where
F: Fn(&$name<V>) $(+ $bound)? + 'static,
{
unsafe extern "C" fn notify_completed_trampoline<V, F>(
this: *mut ffi::GTask,
_param_spec: glib::ffi::gpointer,
f: glib::ffi::gpointer,
) where
V: ValueType $(+ $bound)?,
F: Fn(&$name<V>) + 'static,
{
let f: &F = &*(f as *const F);
f(&from_glib_borrow(this))
}
unsafe {
let f: Box_<F> = Box_::new(f);
connect_raw(
self.as_ptr() as *mut _,
b"notify::completed\0".as_ptr() as *const _,
Some(transmute::<_, unsafe extern "C" fn()>(
notify_completed_trampoline::<V, F> as *const (),
)),
Box_::into_raw(f),
)
}
}
#[doc(alias = "g_task_return_error_if_cancelled")]
#[allow(unused_unsafe)]
pub $($safety)? fn return_error_if_cancelled(&self) -> bool {
unsafe { from_glib(ffi::g_task_return_error_if_cancelled(self.to_glib_none().0)) }
}
#[doc(alias = "g_task_return_value")]
#[doc(alias = "g_task_return_boolean")]
#[doc(alias = "g_task_return_int")]
#[doc(alias = "g_task_return_pointer")]
#[doc(alias = "g_task_return_error")]
#[allow(unused_unsafe)]
pub $($safety)? fn return_result(self, result: Result<V, glib::Error>) {
#[cfg(not(feature = "v2_64"))]
unsafe extern "C" fn value_free(value: *mut libc::c_void) {
let _: glib::Value = from_glib_full(value as *mut glib::gobject_ffi::GValue);
}
match result {
#[cfg(feature = "v2_64")]
Ok(v) => unsafe {
ffi::g_task_return_value(
self.to_glib_none().0,
v.to_value().to_glib_none().0 as *mut _,
)
},
#[cfg(not(feature = "v2_64"))]
Ok(v) => unsafe {
let v: glib::Value = v.into();
ffi::g_task_return_pointer(
self.to_glib_none().0,
<glib::Value as glib::translate::IntoGlibPtr::<*mut glib::gobject_ffi::GValue>>::into_glib_ptr(v) as glib::ffi::gpointer,
Some(value_free),
)
},
Err(e) => unsafe {
ffi::g_task_return_error(self.to_glib_none().0, e.into_glib_ptr());
},
}
}
#[doc(alias = "g_task_propagate_value")]
#[doc(alias = "g_task_propagate_boolean")]
#[doc(alias = "g_task_propagate_int")]
#[doc(alias = "g_task_propagate_pointer")]
#[allow(unused_unsafe)]
pub $($safety)? fn propagate(self) -> Result<V, glib::Error> {
let mut error = ptr::null_mut();
unsafe {
#[cfg(feature = "v2_64")]
{
let mut value = glib::Value::uninitialized();
ffi::g_task_propagate_value(
self.to_glib_none().0,
value.to_glib_none_mut().0,
&mut error,
);
if error.is_null() {
Ok(V::from_value(&value))
} else {
Err(from_glib_full(error))
}
}
#[cfg(not(feature = "v2_64"))]
{
let value = ffi::g_task_propagate_pointer(self.to_glib_none().0, &mut error);
if error.is_null() {
let value = Option::<glib::Value>::from_glib_full(
value as *mut glib::gobject_ffi::GValue,
)
.expect("Task::propagate() called before Task::return_result()");
Ok(V::from_value(&value))
} else {
Err(from_glib_full(error))
}
}
}
}
}
impl <V: ValueType $(+ $bound)?> std::fmt::Display for $name<V> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str(stringify!($name))
}
}
}
}
task_impl!(LocalTask);
task_impl!(Task, @bound: Send, @safety: unsafe);
impl<V: ValueType + Send> Task<V> {
#[doc(alias = "g_task_run_in_thread")]
pub fn run_in_thread<S, Q>(&self, task_func: Q)
where
S: IsA<glib::Object> + Send,
Q: FnOnce(Self, Option<&S>, Option<&Cancellable>) + Send + 'static,
{
let task_func_data = Box_::new(task_func);
unsafe {
assert!(
ffi::g_task_get_task_data(self.to_glib_none().0).is_null(),
"Task data was manually set or the task was run thread multiple times"
);
ffi::g_task_set_task_data(
self.to_glib_none().0,
Box_::into_raw(task_func_data) as *mut _,
None,
);
}
unsafe extern "C" fn trampoline<V, S, Q>(
task: *mut ffi::GTask,
source_object: *mut glib::gobject_ffi::GObject,
user_data: glib::ffi::gpointer,
cancellable: *mut ffi::GCancellable,
) where
V: ValueType + Send,
S: IsA<glib::Object> + Send,
Q: FnOnce(Task<V>, Option<&S>, Option<&Cancellable>) + Send + 'static,
{
let task = Task::from_glib_none(task);
let source_object = Option::<glib::Object>::from_glib_borrow(source_object);
let cancellable = Option::<Cancellable>::from_glib_borrow(cancellable);
let task_func: Box_<Q> = Box::from_raw(user_data as *mut _);
task_func(
task,
source_object.as_ref().as_ref().map(|s| s.unsafe_cast_ref()),
cancellable.as_ref().as_ref(),
);
}
let task_func = trampoline::<V, S, Q>;
unsafe {
ffi::g_task_run_in_thread(self.to_glib_none().0, Some(task_func));
}
}
}
unsafe impl<V: ValueType + Send> Send for Task<V> {}
unsafe impl<V: ValueType + Send> Sync for Task<V> {}
#[derive(Debug)]
pub struct JoinHandle<T> {
rx: oneshot::Receiver<std::thread::Result<T>>,
}
impl<T> JoinHandle<T> {
#[inline]
fn new() -> (Self, oneshot::Sender<std::thread::Result<T>>) {
let (tx, rx) = oneshot::channel();
(Self { rx }, tx)
}
}
impl<T> Future for JoinHandle<T> {
type Output = std::thread::Result<T>;
#[inline]
fn poll(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
std::pin::Pin::new(&mut self.rx)
.poll(cx)
.map(|r| r.unwrap())
}
}
impl<T> futures_core::FusedFuture for JoinHandle<T> {
#[inline]
fn is_terminated(&self) -> bool {
self.rx.is_terminated()
}
}
pub fn spawn_blocking<T, F>(func: F) -> JoinHandle<T>
where
T: Send + 'static,
F: FnOnce() -> T + Send + 'static,
{
let task = unsafe { Task::<bool>::new(Cancellable::NONE, Cancellable::NONE, |_, _| {}) };
let (join, tx) = JoinHandle::new();
task.run_in_thread(move |_, _: Option<&Cancellable>, _| {
let res = panic::catch_unwind(panic::AssertUnwindSafe(func));
let _ = tx.send(res);
});
join
}
#[cfg(test)]
mod test {
use super::*;
use crate::{prelude::*, test_util::run_async_local};
#[test]
fn test_int_async_result() {
match run_async_local(|tx, l| {
let cancellable = crate::Cancellable::new();
let task = unsafe {
crate::LocalTask::new(
None,
Some(&cancellable),
move |t: LocalTask<i32>, _b: Option<&glib::Object>| {
tx.send(t.propagate()).unwrap();
l.quit();
},
)
};
task.return_result(Ok(100_i32));
}) {
Err(_) => panic!(),
Ok(i) => assert_eq!(i, 100),
}
}
#[test]
fn test_object_async_result() {
use glib::subclass::prelude::*;
pub struct MySimpleObjectPrivate {
pub size: std::cell::RefCell<Option<i64>>,
}
#[glib::object_subclass]
impl ObjectSubclass for MySimpleObjectPrivate {
const NAME: &'static str = "MySimpleObjectPrivate";
type Type = MySimpleObject;
fn new() -> Self {
Self {
size: std::cell::RefCell::new(Some(100)),
}
}
}
impl ObjectImpl for MySimpleObjectPrivate {}
glib::wrapper! {
pub struct MySimpleObject(ObjectSubclass<MySimpleObjectPrivate>);
}
impl MySimpleObject {
pub fn new() -> Self {
glib::Object::new()
}
#[doc(alias = "get_size")]
pub fn size(&self) -> Option<i64> {
*self.imp().size.borrow()
}
pub fn set_size(&self, size: i64) {
self.imp().size.borrow_mut().replace(size);
}
}
impl Default for MySimpleObject {
fn default() -> Self {
Self::new()
}
}
match run_async_local(|tx, l| {
let cancellable = crate::Cancellable::new();
let task = unsafe {
crate::LocalTask::new(
None,
Some(&cancellable),
move |t: LocalTask<glib::Object>, _b: Option<&glib::Object>| {
tx.send(t.propagate()).unwrap();
l.quit();
},
)
};
let my_object = MySimpleObject::new();
my_object.set_size(100);
task.return_result(Ok(my_object.upcast::<glib::Object>()));
}) {
Err(_) => panic!(),
Ok(o) => {
let o = o.downcast::<MySimpleObject>().unwrap();
assert_eq!(o.size(), Some(100));
}
}
}
#[test]
fn test_error() {
match run_async_local(|tx, l| {
let cancellable = crate::Cancellable::new();
let task = unsafe {
crate::LocalTask::new(
None,
Some(&cancellable),
move |t: LocalTask<i32>, _b: Option<&glib::Object>| {
tx.send(t.propagate()).unwrap();
l.quit();
},
)
};
task.return_result(Err(glib::Error::new(
crate::IOErrorEnum::WouldBlock,
"WouldBlock",
)));
}) {
Err(e) => match e.kind().unwrap() {
crate::IOErrorEnum::WouldBlock => {}
_ => panic!(),
},
Ok(_) => panic!(),
}
}
#[test]
fn test_cancelled() {
match run_async_local(|tx, l| {
let cancellable = crate::Cancellable::new();
let task = unsafe {
crate::LocalTask::new(
None,
Some(&cancellable),
move |t: LocalTask<i32>, _b: Option<&glib::Object>| {
tx.send(t.propagate()).unwrap();
l.quit();
},
)
};
cancellable.cancel();
task.return_error_if_cancelled();
}) {
Err(e) => match e.kind().unwrap() {
crate::IOErrorEnum::Cancelled => {}
_ => panic!(),
},
Ok(_) => panic!(),
}
}
}