use core::pin::Pin;
use std::sync::Arc;
use alloc::boxed::Box;
use async_channel::{Receiver, Sender};
use futures_util::{FutureExt, StreamExt};
use spin::RwLock;
use crate::BinaryDecode;
use crate::batch::with_runtime;
use crate::function::{CALL_EXPORT_FN_ID, DROP_NATIVE_REF_FN_ID, RustCallback};
use crate::ipc::MessageType;
use crate::ipc::{DecodedData, DecodedVariant, IPCMessage, OutboundIPCMessage};
use crate::object_store::ObjectHandle;
#[derive(Debug, Clone)]
pub struct WryBindgenEvent {
id: u64,
event: AppEventVariant,
}
impl WryBindgenEvent {
pub(crate) fn id(&self) -> u64 {
self.id
}
pub(crate) fn ipc(id: u64, msg: OutboundIPCMessage) -> Self {
Self {
id,
event: AppEventVariant::Ipc(msg),
}
}
pub(crate) fn webview_loaded(id: u64) -> Self {
Self {
id,
event: AppEventVariant::WebviewLoaded,
}
}
pub(crate) fn into_variant(self) -> AppEventVariant {
self.event
}
}
#[derive(Debug, Clone)]
pub(crate) enum AppEventVariant {
Ipc(OutboundIPCMessage),
WebviewLoaded,
}
#[derive(Clone)]
pub(crate) struct IPCSenders {
eval_sender: Sender<IPCMessage>,
respond_sender: futures_channel::mpsc::UnboundedSender<IPCMessage>,
}
impl IPCSenders {
pub(crate) fn start_send(&self, msg: IPCMessage) -> bool {
match msg.ty().unwrap() {
MessageType::Evaluate => self.eval_sender.try_send(msg).is_ok(),
MessageType::Respond => self.respond_sender.unbounded_send(msg).is_ok(),
}
}
}
struct IPCReceivers {
eval_receiver: Pin<Box<Receiver<IPCMessage>>>,
respond_receiver: futures_channel::mpsc::UnboundedReceiver<IPCMessage>,
}
impl IPCReceivers {
pub fn recv_blocking(&mut self) -> Option<IPCMessage> {
pollster::block_on(async {
let Self {
eval_receiver,
respond_receiver,
} = self;
futures_util::select_biased! {
respond_msg = respond_receiver.next().fuse() => {
respond_msg
},
eval_msg = eval_receiver.next().fuse() => {
eval_msg
},
}
})
}
}
pub(crate) struct WryIPC {
pub(crate) proxy: Arc<dyn Fn(WryBindgenEvent) + Send + Sync>,
receivers: RwLock<IPCReceivers>,
}
impl WryIPC {
pub(crate) fn new(proxy: Arc<dyn Fn(WryBindgenEvent) + Send + Sync>) -> (Self, IPCSenders) {
let (eval_sender, eval_receiver) = async_channel::unbounded();
let (respond_sender, respond_receiver) = futures_channel::mpsc::unbounded();
let senders = IPCSenders {
eval_sender,
respond_sender,
};
let receivers = RwLock::new(IPCReceivers {
eval_receiver: Box::pin(eval_receiver),
respond_receiver,
});
let ipc = Self { proxy, receivers };
(ipc, senders)
}
pub(crate) fn js_response(&self, id: u64, responder: OutboundIPCMessage) {
(self.proxy)(WryBindgenEvent::ipc(id, responder));
}
}
pub(crate) fn progress_js_with<O>(
mut with_respond: impl for<'a> FnMut(DecodedData<'a>) -> O,
) -> Option<O> {
let response = with_runtime(|runtime| runtime.ipc().receivers.write().recv_blocking())?;
dispatch_inbound_message(&response, &mut with_respond)
}
pub async fn handle_callbacks() {
let receiver = with_runtime(|runtime| runtime.ipc().receivers.read().eval_receiver.clone());
while let Ok(response) = receiver.recv().await {
dispatch_inbound_message(&response, &mut |_| unreachable!());
}
}
fn dispatch_inbound_message<O>(
response: &IPCMessage,
with_respond: &mut impl for<'a> FnMut(DecodedData<'a>) -> O,
) -> Option<O> {
let decoder = response.decoded().expect("Failed to decode response");
match decoder {
DecodedVariant::Respond { data } => {
with_runtime(|runtime| {
runtime.pop_and_ack_type_cache_frame();
});
let result = with_respond(data);
Some(result)
}
DecodedVariant::Evaluate { data } => {
handle_inbound_evaluate(data);
None
}
}
}
fn handle_inbound_evaluate(mut data: DecodedData<'_>) {
let _eval = InboundEvaluateGuard::new();
handle_rust_callback(&mut data);
}
fn handle_rust_callback(data: &mut DecodedData) {
let fn_id = data.take_u32().expect("Failed to read fn_id");
let response = match fn_id {
0 => {
let key = data.take_u32().unwrap();
let callback = with_runtime(|state| {
let rust_callback = state.get_object::<RustCallback>(key);
rust_callback.clone_rc()
});
let _frame = BorrowFrameGuard::new();
let mut encoder = respond_encoder();
match (callback)(data, &mut encoder) {
Ok(()) => finish_respond_message(encoder),
Err(err) => {
panic!("Rust callback {key} failed to decode arguments: {err}")
}
}
}
DROP_NATIVE_REF_FN_ID => {
let key = ObjectHandle::decode(data).expect("Failed to decode object handle");
crate::object_store::drop_object(key);
finish_respond_message(respond_encoder())
}
CALL_EXPORT_FN_ID => {
let export_name: alloc::string::String =
crate::encode::BinaryDecode::decode(data).expect("Failed to decode export name");
let export = crate::__rt::inventory::iter::<crate::__rt::JsExportSpec>()
.find(|e| e.name == export_name)
.unwrap_or_else(|| panic!("Unknown export: {export_name}"));
let result = (export.handler)(data);
assert!(data.is_empty(), "Extra data remaining after export call");
match result {
Ok(encoded) => {
let mut encoder = respond_encoder();
encoder.extend(&encoded);
finish_respond_message(encoder)
}
Err(err) => {
panic!("Export call failed: {err}");
}
}
}
_ => panic!("Unknown Rust callback function ID: {fn_id}"),
};
with_runtime(|runtime| runtime.ipc().js_response(runtime.webview_id(), response));
}
struct BorrowFrameGuard;
impl BorrowFrameGuard {
fn new() -> Self {
with_runtime(|state| state.push_borrow_frame());
Self
}
}
impl Drop for BorrowFrameGuard {
fn drop(&mut self) {
with_runtime(|state| state.pop_borrow_frame());
}
}
struct InboundEvaluateGuard;
impl InboundEvaluateGuard {
fn new() -> Self {
with_runtime(|state| state.enter_inbound_evaluate());
Self
}
}
impl Drop for InboundEvaluateGuard {
fn drop(&mut self) {
with_runtime(|state| state.leave_inbound_evaluate());
}
}
fn respond_encoder() -> crate::ipc::EncodedData {
let mut encoder = crate::ipc::EncodedData::new();
encoder.push_u8(MessageType::Respond as u8);
encoder
}
fn finish_respond_message(encoder: crate::ipc::EncodedData) -> OutboundIPCMessage {
with_runtime(|runtime| runtime.finish_respond_message(encoder))
}