//! A multiple-argument dispatch system for our RPC system.
//!
//! Our RPC functionality is polymorphic in Methods (what we're told to do) and
//! Objects (the things that we give the methods to); we want to be able to
//! provide different implementations for each method, on each object.
//!
//! ## Writing RPC functions
//!
//!
//! To participate in this system, an RPC function must have a particular type:
//! ```rust,ignore
//! async fn my_rpc_func(
//! target: Arc,
//! method: Box,
//! ctx: Box,
//! [ updates: rpc::UpdateSink Result>
//! { ... }
//! ```
//!
//! If the "updates" argument is present,
//! then you will need to use the `[Updates]` flag when registering this function.
//!
//! ## Registering RPC functions statically
//!
//! After writing a function in the form above,
//! you need to register it with the RPC system so that it can be invoked on objects of the right type.
//! The easiest way to do so is by registering it, using [`static_rpc_invoke_fn!`](crate::static_rpc_invoke_fn):
//!
//! ```rust,ignore
//! static_rpc_invoke_fn!{ my_rpc_func; my_other_rpc_func; }
//! ```
//!
//! You can register particular instantiations of generic types, if they're known ahead of time:
//! ```rust,ignore
//! static_rpc_invoke_fn!{ my_generic_fn::; }
//! ```
//!
//! ## Registering RPC functions at runtime.
//!
//! If you can't predict all the instantiations of your function in advance,
//! you can insert them into a [`DispatchTable`] at run time:
//! ```rust,ignore
//! fn install_my_rpc_methods(table: &mut DispatchTable) {
//! table.insert(invoker_ent!(my_generic_fn::));
//! table.insert(invoker_ent!(my_generic_fn_with_update::));
//! }
//! ```
use std::any;
use std::collections::HashMap;
use std::pin::Pin;
use std::sync::Arc;
use futures::future::BoxFuture;
use futures::Sink;
use void::Void;
use crate::{Context, DynMethod, Object, RpcError, SendUpdateError};
/// A type-erased serializable value.
#[doc(hidden)]
pub type RpcValue = Box;
/// The return type from an RPC function.
#[doc(hidden)]
pub type RpcResult = Result;
/// The return type from sending an update.
#[doc(hidden)]
pub type RpcSendResult = Result;
/// A boxed future holding the result of an RPC method.
type RpcResultFuture = BoxFuture<'static, RpcResult>;
/// A boxed sink on which updates can be sent.
pub type BoxedUpdateSink = Pin + Send>>;
/// A boxed sink on which updates of a particular type can be sent.
//
// NOTE: I'd like our functions to be able to take `impl Sink` instead,
// but that doesn't work with our macro nonsense.
// Instead, we might choose to specialize `Invoker` if we find that the
// extra boxing in this case ever matters.
pub type UpdateSink = Pin + Send + 'static>>;
/// Type returned by DispatchTable::invoke_special, to represent a future containing
/// a type-erased type.
type SpecialResultFuture = BoxFuture<'static, Box>;
/// An installable handler for running a method on an object type.
///
/// Callers should not typically implement this trait directly;
/// instead, use one of its blanket implementations.
//
// (This trait isn't sealed because there _are_ theoretical reasons
// why you might want to provide a special implementation.)
pub trait Invocable: Send + Sync + 'static {
/// Return the type of object that this Invokable will accept.
fn object_type(&self) -> any::TypeId;
/// Return the type of method that this Invocable will accept.
fn method_type(&self) -> any::TypeId;
/// Describe the types for this Invocable. Used for debugging.
fn describe_invocable(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
/// Invoke this method on an object.
///
/// Requires that `obj` has the type `self.object_type()`,
/// and that `method` has the type `self.method_type()`.
///
/// Unlike `RpcInvocable::invoke()`, does not convert the resulting types
/// into serializable formats, and does not require that they _can be_
/// so converted.
fn invoke_special(
&self,
obj: Arc,
method: Box,
ctx: Box,
) -> Result;
}
/// Subtrait of `Invocable` that requires its outputs to be serializable as RPC replies.
pub trait RpcInvocable: Invocable {
/// Invoke a method on an object.
///
/// Requires that `obj` has the type `self.object_type()`,
/// and that `method` has the type `self.method_type()`.
fn invoke(
&self,
obj: Arc,
method: Box,
ctx: Box,
sink: BoxedUpdateSink,
) -> Result;
}
/// Helper: Declare a blanket implementation for Invocable.
///
/// We provide two blanket implementations:
/// Once over a fn() taking an update sink,
/// and once over a fn() not taking an update sink.
macro_rules! declare_invocable_impl {
{
// These arguments are used to fill in some blanks that we need to use
// when handling an update sink.
$( update_gen: $update_gen:ident,
update_arg: { $sink:ident: $update_arg:ty } ,
update_arg_where: { $($update_arg_where:tt)+ } ,
sink_fn: $sink_fn:expr
)?
} => {
impl Invocable
for fn(Arc, Box, Box $(, $update_arg )? ) -> Fut
where
M: crate::Method,
OBJ: Object,
S: 'static,
E: 'static,
M::Output: From,
M::Error: From,
Fut: futures::Future