use super::*;
use ::gstd::{
errors::Error,
msg::{CreateProgramFuture, MessageFuture},
};
#[derive(Default)]
pub struct GstdParams {
#[cfg(not(feature = "ethexe"))]
pub gas_limit: Option<GasUnit>,
pub value: Option<ValueUnit>,
pub wait_up_to: Option<BlockCount>,
#[cfg(not(feature = "ethexe"))]
pub reply_deposit: Option<GasUnit>,
#[cfg(not(feature = "ethexe"))]
pub reply_hook: Option<Box<dyn FnOnce() + Send + 'static>>,
pub redirect_on_exit: bool,
}
crate::params_for_pending_impl!(GstdEnv, GstdParams {
#[cfg(not(feature = "ethexe"))]
pub gas_limit: GasUnit,
pub value: ValueUnit,
pub wait_up_to: BlockCount,
#[cfg(not(feature = "ethexe"))]
pub reply_deposit: GasUnit,
});
impl GstdParams {
pub fn with_redirect_on_exit(self, redirect_on_exit: bool) -> Self {
Self {
redirect_on_exit,
..self
}
}
#[cfg(not(feature = "ethexe"))]
pub fn with_reply_hook<F: FnOnce() + Send + 'static>(self, f: F) -> Self {
Self {
reply_hook: Some(Box::new(f)),
..self
}
}
}
impl<T: ServiceCall> PendingCall<T, GstdEnv> {
pub fn with_redirect_on_exit(self, redirect_on_exit: bool) -> Self {
self.with_params(|params| params.with_redirect_on_exit(redirect_on_exit))
}
#[cfg(not(feature = "ethexe"))]
pub fn with_reply_hook<F: FnOnce() + Send + 'static>(self, f: F) -> Self {
self.with_params(|params| params.with_reply_hook(f))
}
}
#[derive(Debug, Default, Clone)]
pub struct GstdEnv;
impl GearEnv for GstdEnv {
type Params = GstdParams;
type Error = Error;
#[cfg(target_arch = "wasm32")]
type MessageState = GstdFuture;
#[cfg(not(target_arch = "wasm32"))]
type MessageState = core::future::Ready<Result<Vec<u8>, Self::Error>>;
}
impl ReplyError for Error {
fn from_codec_error(err: parity_scale_codec::Error) -> Self {
Error::Decode(err)
}
fn userspace_panic_payload(&self) -> Option<&[u8]> {
match self {
Error::ErrorReply(
payload,
ErrorReplyReason::Execution(SimpleExecutionError::UserspacePanic),
) => Some(payload.0.as_ref()),
_ => None,
}
}
}
impl GstdEnv {
pub fn send_one_way(
&self,
destination: ActorId,
payload: impl AsRef<[u8]>,
params: GstdParams,
) -> Result<MessageId, Error> {
let value = params.value.unwrap_or_default();
let payload_bytes = payload.as_ref();
#[cfg(not(feature = "ethexe"))]
let waiting_reply_to = if let Some(gas_limit) = params.gas_limit {
::gcore::msg::send_with_gas(destination, payload_bytes, gas_limit, value)?
} else {
::gcore::msg::send(destination, payload_bytes, value)?
};
#[cfg(feature = "ethexe")]
let waiting_reply_to = ::gcore::msg::send(destination, payload_bytes, value)?;
#[cfg(not(feature = "ethexe"))]
if let Some(reply_deposit) = params.reply_deposit {
::gcore::exec::reply_deposit(waiting_reply_to, reply_deposit)?;
}
Ok(waiting_reply_to)
}
}
impl<T: ServiceCall> PendingCall<T, GstdEnv> {
pub fn send_one_way(&mut self) -> Result<MessageId, Error> {
let (payload, params) = self.take_encoded_args_and_params();
self.env.send_one_way(self.destination, payload, params)
}
}
#[cfg(target_arch = "wasm32")]
const _: () = {
use core::task::ready;
#[cfg(not(feature = "ethexe"))]
#[inline]
fn send_for_reply_future(
destination: ActorId,
payload: &[u8],
params: &mut GstdParams,
) -> Result<MessageFuture, Error> {
let value = params.value.unwrap_or_default();
let reply_deposit = params.reply_deposit.unwrap_or_default();
let mut message_future = if let Some(gas_limit) = params.gas_limit {
::gstd::msg::send_bytes_with_gas_for_reply(
destination,
payload,
gas_limit,
value,
reply_deposit,
)?
} else {
::gstd::msg::send_bytes_for_reply(destination, payload, value, reply_deposit)?
};
message_future = message_future.up_to(params.wait_up_to)?;
if let Some(reply_hook) = params.reply_hook.take() {
message_future = message_future.handle_reply(reply_hook)?;
}
Ok(message_future)
}
#[cfg(feature = "ethexe")]
#[inline]
fn send_for_reply_future(
destination: ActorId,
payload: &[u8],
params: &mut GstdParams,
) -> Result<MessageFuture, Error> {
let value = params.value.unwrap_or_default();
let mut message_future = ::gstd::msg::send_bytes_for_reply(destination, payload, value)?;
message_future = message_future.up_to(params.wait_up_to)?;
Ok(message_future)
}
#[inline]
fn send_for_reply(
destination: ActorId,
payload: Vec<u8>,
params: &mut GstdParams,
) -> Result<GstdFuture, Error> {
let future = send_for_reply_future(destination, payload.as_ref(), params)?;
if params.redirect_on_exit {
let created_block = params.wait_up_to.map(|_| gstd::exec::block_height());
Ok(GstdFuture::MessageWithRedirect {
created_block,
future,
destination,
payload,
})
} else {
Ok(GstdFuture::Message { future })
}
}
impl<T: ServiceCall> PendingCall<T, GstdEnv> {
pub fn send_for_reply(mut self) -> Result<Self, Error> {
if self.state.is_some() {
panic!("{PENDING_CALL_INVALID_STATE}");
}
let args = self
.args
.take()
.unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}"));
let payload = T::encode_call(&self.route, &args);
let params = self.params.get_or_insert_default();
let destination = self.destination;
let future = send_for_reply(destination, payload, params)?;
self.state = Some(future);
Ok(self)
}
}
impl<T: ServiceCall> Future for PendingCall<T, GstdEnv> {
type Output = Result<T::Output, <GstdEnv as GearEnv>::Error>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.state.is_none() {
let args = self
.args
.as_ref()
.unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}"));
let payload = T::encode_call(&self.route, args);
let destination = self.destination;
let params = self.params.get_or_insert_default();
let future = send_for_reply(destination, payload, params)?;
self.state = Some(future);
return Poll::Pending;
}
let this = self.as_mut().project();
let mut state = unsafe { this.state.as_pin_mut().unwrap_unchecked() };
let output = match state.as_mut().project() {
Projection::Message { future } => ready!(future.poll(cx)),
Projection::MessageWithRedirect { future, .. } => ready!(future.poll(cx)),
_ => panic!("{PENDING_CALL_INVALID_STATE}"),
};
match output {
Err(gstd::errors::Error::ErrorReply(
error_payload,
ErrorReplyReason::UnavailableActor(SimpleUnavailableActorError::ProgramExited),
)) => {
let params = this.params.get_or_insert_default();
if let Replace::MessageWithRedirect {
destination: _destination,
created_block,
payload,
..
} = state.as_mut().project_replace(GstdFuture::Dummy)
&& params.redirect_on_exit
&& let Ok(new_target) = ActorId::try_from(error_payload.0.as_ref())
{
gstd::debug!("Redirecting message from {_destination} to {new_target}");
params.wait_up_to = params.wait_up_to.and_then(|wait_up_to| {
created_block.map(|created_block| {
let current_block = gstd::exec::block_height();
wait_up_to
.saturating_sub(current_block.saturating_sub(created_block))
})
});
let future = send_for_reply(new_target, payload, params)?;
_ = state.as_mut().project_replace(future);
Poll::Pending
} else {
Poll::Ready(Err(gstd::errors::Error::ErrorReply(
error_payload,
ErrorReplyReason::UnavailableActor(
SimpleUnavailableActorError::ProgramExited,
),
)))
}
}
output => Poll::Ready(decode_reply_or_throw::<T, _>(this.route, output)),
}
}
}
impl<A, T> Future for PendingCtor<A, T, GstdEnv>
where
T: ServiceCall,
T::Output: PendingCtorOutput<A, GstdEnv>,
{
type Output = Result<
<T::Output as PendingCtorOutput<A, GstdEnv>>::Output,
<GstdEnv as GearEnv>::Error,
>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.state.is_none() {
let params = self.params.take().unwrap_or_default();
let value = params.value.unwrap_or_default();
let salt = self.salt.take().unwrap();
let args = self
.args
.as_ref()
.unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}"));
let payload = T::encode_call(&self.route, args);
#[cfg(not(feature = "ethexe"))]
let future = if let Some(gas_limit) = params.gas_limit {
::gstd::prog::create_program_bytes_with_gas_for_reply(
self.code_id,
salt,
payload,
gas_limit,
value,
params.reply_deposit.unwrap_or_default(),
)?
} else {
::gstd::prog::create_program_bytes_for_reply(
self.code_id,
salt,
payload,
value,
params.reply_deposit.unwrap_or_default(),
)?
};
#[cfg(feature = "ethexe")]
let future = ::gstd::prog::create_program_bytes_for_reply(
self.code_id,
salt,
payload,
value,
)?;
self.state = Some(GstdFuture::CreateProgram { future });
return Poll::Pending;
}
let this = self.as_mut().project();
let state = unsafe { this.state.as_pin_mut().unwrap_unchecked() };
if let Projection::CreateProgram { future } = state.project() {
let (reply, program_id) = match ready!(future.poll(cx)) {
Ok((program_id, payload)) => (Ok(payload), program_id),
Err(err) => (Err(err), ActorId::zero()),
};
match decode_reply_or_throw::<T, _>(this.route, reply) {
Ok(output) => Poll::Ready(Ok(output.map_result(this.env.clone(), program_id))),
Err(err) => Poll::Ready(Err(err)),
}
} else {
panic!("{PENDING_CTOR_INVALID_STATE}");
}
}
}
};
pin_project_lite::pin_project! {
#[project = Projection]
#[project_replace = Replace]
pub enum GstdFuture {
CreateProgram { #[pin] future: CreateProgramFuture },
Message { #[pin] future: MessageFuture },
MessageWithRedirect {
#[pin]
future: MessageFuture,
destination: ActorId,
created_block: Option<BlockCount>,
payload: Vec<u8>, },
Dummy,
}
}
#[cfg(not(target_arch = "wasm32"))]
const _: () = {
impl<T: ServiceCall> PendingCall<T, GstdEnv>
where
T::Output: Encode + Decode,
T::Route: Default,
{
pub fn from_output(output: T::Output) -> Self {
Self::from_result(Ok(output))
}
pub fn from_error(err: <GstdEnv as GearEnv>::Error) -> Self {
Self::from_result(Err(err))
}
pub fn from_result(res: Result<T::Output, <GstdEnv as GearEnv>::Error>) -> Self {
PendingCall {
env: GstdEnv,
destination: ActorId::zero(),
route: T::Route::default(),
params: None,
args: None,
state: Some(future::ready(res.map(|v| v.encode()))),
}
}
pub fn send_for_reply(mut self) -> Result<Self, Error> {
let _ = self.send_one_way()?;
Ok(self)
}
}
impl<T: ServiceCall<Output = O>, O> From<O> for PendingCall<T, GstdEnv>
where
O: Encode + Decode,
T::Route: Default,
{
fn from(value: O) -> Self {
PendingCall::from_output(value)
}
}
impl<T: ServiceCall> Future for PendingCall<T, GstdEnv>
where
T::Output: Decode,
{
type Output = Result<T::Output, <GstdEnv as GearEnv>::Error>;
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.state.take() {
Some(ready) => {
let output = ready
.into_inner()
.and_then(|v| T::Output::decode(&mut v.as_ref()).map_err(Error::Decode));
Poll::Ready(output)
}
None => panic!("{PENDING_CALL_INVALID_STATE}"),
}
}
}
impl<A, T> Future for PendingCtor<A, T, GstdEnv>
where
T: ServiceCall,
T::Output: PendingCtorOutput<A, GstdEnv>,
{
type Output = Result<
<T::Output as PendingCtorOutput<A, GstdEnv>>::Output,
<GstdEnv as GearEnv>::Error,
>;
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.state.take() {
Some(_ready) => {
let program_id = self
.program_id
.take()
.unwrap_or_else(|| panic!("{PENDING_CTOR_INVALID_STATE}"));
let env = self.env.clone();
let reply = T::decode_reply(&self.route, []).map_err(Error::Decode)?;
Poll::Ready(Ok(reply.map_result(env, program_id)))
}
None => panic!("{PENDING_CTOR_INVALID_STATE}"),
}
}
}
};