use candid::Principal;
use ic_kit_sys::ic0;
use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::{AtomicBool, Ordering};
use std::task::{Context, Poll, Waker};
#[cfg(target_family = "wasm")]
#[allow(dead_code)]
mod rc {
use std::cell::{RefCell, RefMut};
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
pub(crate) type InnerCell<T> = RefCell<T>;
pub(crate) struct WasmCell<T>(Rc<InnerCell<T>>);
unsafe impl<T> Send for WasmCell<T> {}
unsafe impl<T> Sync for WasmCell<T> {}
impl<T> WasmCell<T> {
pub fn new(val: T) -> Self {
WasmCell(Rc::new(InnerCell::new(val)))
}
pub fn into_raw(self) -> *const InnerCell<T> {
Rc::into_raw(self.0)
}
#[allow(clippy::missing_safety_doc)]
pub unsafe fn from_raw(ptr: *const InnerCell<T>) -> Self {
Self(Rc::from_raw(ptr))
}
pub fn borrow_mut(&self) -> RefMut<'_, T> {
self.0.borrow_mut()
}
pub fn as_ptr(&self) -> *const InnerCell<T> {
self.0.as_ptr() as *const _
}
}
impl<O, T: Future<Output = O>> Future for WasmCell<T> {
type Output = O;
#[allow(unused_mut)]
fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
unsafe { Pin::new_unchecked(&mut *self.0.borrow_mut()) }.poll(ctx)
}
}
impl<T> Clone for WasmCell<T> {
fn clone(&self) -> Self {
WasmCell(Rc::clone(&self.0))
}
}
}
#[cfg(not(target_family = "wasm"))]
#[allow(dead_code)]
mod rc {
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex, MutexGuard};
use std::task::{Context, Poll};
pub(crate) type InnerCell<T> = Mutex<T>;
pub(crate) struct WasmCell<T>(Arc<InnerCell<T>>);
impl<T> WasmCell<T> {
pub fn new(val: T) -> Self {
WasmCell(Arc::new(InnerCell::new(val)))
}
pub fn into_raw(self) -> *const InnerCell<T> {
Arc::into_raw(self.0)
}
#[allow(clippy::missing_safety_doc)]
pub unsafe fn from_raw(ptr: *const InnerCell<T>) -> Self {
Self(Arc::from_raw(ptr))
}
pub fn borrow_mut(&self) -> MutexGuard<'_, T> {
self.0.lock().unwrap()
}
pub fn as_ptr(&self) -> *const InnerCell<T> {
Arc::<_>::as_ptr(&self.0)
}
}
impl<O, T: Future<Output = O>> Future for WasmCell<T> {
type Output = O;
#[allow(unused_mut)]
fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
unsafe { Pin::new_unchecked(&mut *self.0.lock().unwrap()) }.poll(ctx)
}
}
impl<T> Clone for WasmCell<T> {
fn clone(&self) -> Self {
WasmCell(Arc::clone(&self.0))
}
}
}
use rc::{InnerCell, WasmCell};
struct CallFutureState {
ready: bool,
waker: Option<Waker>,
}
pub(crate) struct CallFuture {
state: WasmCell<CallFutureState>,
}
impl Future for CallFuture {
type Output = ();
fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
let self_ref = Pin::into_ref(self);
let mut state = self_ref.state.borrow_mut();
if state.ready {
Poll::Ready(())
} else {
state.waker = Some(context.waker().clone());
Poll::Pending
}
}
}
impl CallFuture {
pub fn mark_ready(self) -> Self {
{
self.state.borrow_mut().ready = true;
}
self
}
pub fn is_ready(&self) -> bool {
self.state.borrow_mut().ready
}
}
pub(crate) unsafe fn call_new(canister_id: Principal, method: &str) -> CallFuture {
let callee = canister_id.as_slice();
let state = WasmCell::new(CallFutureState {
ready: false,
waker: None,
});
let state_ptr = WasmCell::into_raw(state.clone());
ic0::call_new(
callee.as_ptr() as isize,
callee.len() as isize,
method.as_ptr() as isize,
method.len() as isize,
callback as usize as isize,
state_ptr as isize,
callback as usize as isize,
state_ptr as isize,
);
ic0::call_on_cleanup(cleanup as usize as isize, state_ptr as isize);
CallFuture { state }
}
fn callback(state_ptr: *const InnerCell<CallFutureState>) {
let state = unsafe { WasmCell::from_raw(state_ptr) };
{
state.borrow_mut().ready = true;
}
let w = state.borrow_mut().waker.take();
if let Some(waker) = w {
waker.wake()
}
}
fn cleanup(state_ptr: *const InnerCell<CallFutureState>) {
let state = unsafe { WasmCell::from_raw(state_ptr) };
state.borrow_mut().ready = true;
let w = state.borrow_mut().waker.take();
if let Some(waker) = w {
CLEANUP.store(true, Ordering::Relaxed);
waker.wake();
CLEANUP.store(false, Ordering::Relaxed);
}
}
#[inline]
pub fn spawn<F: 'static + Future<Output = ()>>(future: F) {
let future_ptr = Box::into_raw(Box::new(future));
let future_ptr_ptr: *mut *mut dyn Future<Output = ()> = Box::into_raw(Box::new(future_ptr));
let mut pinned_future = unsafe { Pin::new_unchecked(&mut *future_ptr) };
if pinned_future
.as_mut()
.poll(&mut Context::from_waker(&waker::waker(
future_ptr_ptr as *const (),
)))
.is_ready()
{
unsafe {
let _ = Box::from_raw(future_ptr);
let _ = Box::from_raw(future_ptr_ptr);
}
}
}
pub(crate) static CLEANUP: AtomicBool = AtomicBool::new(false);
mod waker {
use super::*;
use std::{
sync::atomic::Ordering,
task::{RawWaker, RawWakerVTable, Waker},
};
type FuturePtr = *mut dyn Future<Output = ()>;
static MY_VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
#[inline(always)]
fn raw_waker(ptr: *const ()) -> RawWaker {
RawWaker::new(ptr, &MY_VTABLE)
}
#[inline(always)]
fn clone(ptr: *const ()) -> RawWaker {
raw_waker(ptr)
}
#[inline(always)]
unsafe fn wake(ptr: *const ()) {
let boxed_future_ptr_ptr = Box::from_raw(ptr as *mut FuturePtr);
let future_ptr: FuturePtr = *boxed_future_ptr_ptr;
let boxed_future = Box::from_raw(future_ptr);
let mut pinned_future = Pin::new_unchecked(&mut *future_ptr);
if !CLEANUP.load(Ordering::Relaxed)
&& pinned_future
.as_mut()
.poll(&mut Context::from_waker(&waker::waker(ptr)))
.is_pending()
{
Box::into_raw(boxed_future_ptr_ptr);
Box::into_raw(boxed_future);
}
}
#[inline(always)]
fn wake_by_ref(_: *const ()) {}
#[inline(always)]
fn drop(_: *const ()) {}
#[inline(always)]
pub fn waker(ptr: *const ()) -> Waker {
unsafe { Waker::from_raw(raw_waker(ptr)) }
}
}