use futures_util::{
FutureExt,
task::{ArcWake, waker},
};
use send_wrapper::SendWrapper;
use std::{
cell::Cell,
mem::ManuallyDrop,
pin::Pin,
rc::Rc,
sync::Arc,
task::{Context, Poll},
};
pub type Action = Box<dyn FnOnce() + Send + Sync>;
pub trait IdleRunner: Send + Sync {
fn idle_run(&self, f: Action) -> Result<(), Action>;
}
pub struct IdleTask {
runner: Box<dyn IdleRunner>,
#[allow(clippy::type_complexity)]
future: Cell<Option<SendWrapper<Pin<Box<dyn Future<Output = ()> + 'static>>>>>,
}
impl IdleTask {
pub fn new<RUN, FUT>(runner: RUN, f: FUT) -> IdleTask
where
RUN: IdleRunner + 'static,
FUT: Future<Output = ()> + 'static,
{
IdleTask {
runner: Box::new(runner),
future: Cell::new(Some(SendWrapper::new(Box::pin(f)))),
}
}
pub fn into_future(&self) -> Option<impl Future<Output = ()>> {
self.future.take().map(|s| s.take())
}
}
impl Drop for IdleTask {
fn drop(&mut self) {
if let Some(future) = self.future.take() {
if future.valid() {
drop(future);
} else {
let err = self.runner.idle_run(Box::new(move || {
let _ = future;
}));
if let Err(e) = err {
log::warn!(target: "easy_imgui", "future leaked in drop");
std::mem::forget(e);
}
}
}
}
}
unsafe impl Sync for IdleTask {}
impl ArcWake for IdleTask {
fn wake_by_ref(arc_self: &Arc<Self>) {
let task = arc_self.clone();
let err = arc_self.runner.idle_run(Box::new(move || {
let waker = waker(task.clone());
let mut op_future = task.future.take();
if let Some(future) = op_future.as_mut() {
let mut ctx = Context::from_waker(&waker);
match future.as_mut().poll(&mut ctx) {
Poll::Ready(()) => {}
Poll::Pending => {
task.future.set(op_future);
}
}
}
}));
if let Err(e) = err {
log::warn!(target: "easy_imgui", "future leaked in poll");
std::mem::forget(e);
}
}
}
pub struct FutureHandle<T> {
task: std::sync::Weak<IdleTask>,
res: Rc<Cell<Option<T>>>,
}
impl<T> FutureHandle<T> {
pub fn cancel(self) -> Option<T> {
if let Some(task) = self.task.upgrade() {
task.into_future();
}
self.res.take()
}
pub fn result(&self) -> Option<T> {
self.res.take()
}
pub fn has_finished(&self) -> bool {
match self.res.take() {
None => false,
Some(x) => {
self.res.set(Some(x));
true
}
}
}
pub async fn future(self) -> T {
if let Some(task) = self.task.upgrade()
&& let Some(fut) = task.into_future()
{
fut.await;
}
self.res.take().unwrap()
}
pub fn guard(self) -> FutureHandleGuard<T> {
FutureHandleGuard(ManuallyDrop::new(self))
}
}
pub struct FutureHandleGuard<T>(ManuallyDrop<FutureHandle<T>>);
impl<T> Drop for FutureHandleGuard<T> {
fn drop(&mut self) {
let h = unsafe { ManuallyDrop::take(&mut self.0) };
h.cancel();
}
}
impl<T> FutureHandleGuard<T> {
pub fn into_inner(mut self) -> FutureHandle<T> {
let res = unsafe { ManuallyDrop::take(&mut self.0) };
std::mem::forget(self);
res
}
}
pub unsafe fn spawn_idle<T, F, R>(runner: R, future: F) -> FutureHandle<T>
where
T: 'static,
F: Future<Output = T> + 'static,
R: IdleRunner + 'static,
{
let res: Rc<Cell<Option<T>>> = Rc::new(Cell::new(None));
let task = Arc::new(IdleTask::new(
runner,
future.map({
let res = res.clone();
move |t| res.set(Some(t))
}),
));
let wtask = Arc::downgrade(&task);
ArcWake::wake_by_ref(&task);
FutureHandle { task: wtask, res }
}
pub struct FutureBackCallerImpl<T> {
pd: std::marker::PhantomData<*const T>,
}
impl<T> Default for FutureBackCallerImpl<T> {
fn default() -> Self {
FutureBackCallerImpl {
pd: std::marker::PhantomData,
}
}
}
impl<T> FutureBackCallerImpl<T> {
pub fn prepare<ID: 'static>(t: T, f: impl FnOnce()) {
let the_app = TheAppThreadLocal {
type_id: std::any::TypeId::of::<ID>(),
ptr: std::ptr::NonNull::from(&t).cast(),
};
let old = THE_APP.replace(Some(the_app));
let _guard = TheAppGuard(old);
f();
}
pub unsafe fn run<ID, F, R>(&self, f: F) -> Option<R>
where
ID: 'static,
F: FnOnce(&mut T) -> R,
{
let the_app = THE_APP.take();
let guard = TheAppGuard(the_app);
let Some(the_app) = &guard.0 else {
return None;
};
let type_id = std::any::TypeId::of::<ID>();
if type_id != the_app.type_id {
return None;
}
let mut ptr = the_app.ptr.cast::<T>();
let t = unsafe { ptr.as_mut() };
let res = f(t);
Some(res)
}
}
struct TheAppThreadLocal {
type_id: std::any::TypeId,
ptr: std::ptr::NonNull<()>,
}
thread_local! {
static THE_APP: std::cell::Cell<Option<TheAppThreadLocal>> = const { std::cell::Cell::new(None) };
}
struct TheAppGuard(Option<TheAppThreadLocal>);
impl Drop for TheAppGuard {
fn drop(&mut self) {
THE_APP.set(self.0.take());
}
}