crux_core/bridge/mod.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
mod registry;
mod request_serde;
use bincode::{DefaultOptions, Options};
use erased_serde::Serialize as _;
use serde::{Deserialize, Serialize};
use crate::Effect;
use crate::{App, Core};
use registry::{EffectId, ResolveRegistry};
// ResolveByte is public to be accessible from crux_macros
#[doc(hidden)]
pub use request_serde::ResolveSerialized;
/// Request for a side-effect passed from the Core to the Shell. The `EffectId` links
/// the `Request` with the corresponding call to [`Core::resolve`] to pass the data back
/// to the [`App::update`] function (wrapped in the event provided to the capability originating the effect).
// used in docs/internals/bridge.md
// ANCHOR: request
#[derive(Debug, Serialize, Deserialize)]
pub struct Request<Eff>
where
Eff: Serialize,
{
pub id: EffectId,
pub effect: Eff,
}
// ANCHOR_END: request
/// Bridge is a core wrapper presenting the same interface as the [`Core`] but in a
/// serialized form, using bincode as the serialization format.
pub struct Bridge<Eff, A>
where
Eff: Effect,
A: App,
{
inner: BridgeWithSerializer<Eff, A>,
}
impl<Eff, A> Bridge<Eff, A>
where
Eff: Effect + Send + 'static,
A: App,
{
/// Create a new Bridge using the provided `core`.
pub fn new(core: Core<Eff, A>) -> Self {
Self {
inner: BridgeWithSerializer::new(core),
}
}
/// Receive an event from the shell.
///
/// The `event` is serialized and will be deserialized by the core before it's passed
/// to your app.
pub fn process_event(&self, event: &[u8]) -> Vec<u8>
where
A::Event: for<'a> Deserialize<'a>,
{
let options = Self::bincode_options();
let mut deser = bincode::Deserializer::from_slice(event, options);
let mut return_buffer = vec![];
let mut ser = bincode::Serializer::new(&mut return_buffer, options);
self.inner.process_event(&mut deser, &mut ser);
return_buffer
}
/// Receive a response to a capability request from the shell.
///
/// The `output` is serialized capability output. It will be deserialized by the core.
/// The `id` MUST match the `id` of the effect that triggered it, else the core will panic.
// used in docs/internals/bridge.md
// ANCHOR: handle_response_sig
pub fn handle_response(&self, id: u32, output: &[u8]) -> Vec<u8>
// ANCHOR_END: handle_response_sig
where
A::Event: for<'a> Deserialize<'a>,
{
let options = Self::bincode_options();
let mut deser = bincode::Deserializer::from_slice(output, options);
let mut return_buffer = vec![];
let mut ser = bincode::Serializer::new(&mut return_buffer, options);
self.inner.handle_response(id, &mut deser, &mut ser);
return_buffer
}
/// Get the current state of the app's view model (serialized).
pub fn view(&self) -> Vec<u8> {
let options = Self::bincode_options();
let mut return_buffer = vec![];
self.inner
.view(&mut bincode::Serializer::new(&mut return_buffer, options));
return_buffer
}
fn bincode_options() -> impl bincode::Options + Copy {
DefaultOptions::new()
.with_fixint_encoding()
.allow_trailing_bytes()
}
}
/// A bridge with a user supplied serializer
///
/// This is exactly the same as [`Bridge`], except instead of using the default
/// bincode serialization, you can provide your own [`Serializer`].
///
/// **Warning**: the support for custom serialization is **experimental** and
/// does not have a corresponding type generation support - you will need
/// to write deserialization code on the shell side yourself, or generate
/// it using separate tooling.
// used in docs/internals/bridge.md
// ANCHOR: bridge_with_serializer
pub struct BridgeWithSerializer<Eff, A>
where
Eff: Effect,
A: App,
{
core: Core<Eff, A>,
registry: ResolveRegistry,
}
// ANCHOR_END: bridge_with_serializer
impl<Eff, A> BridgeWithSerializer<Eff, A>
where
Eff: Effect,
A: App,
{
pub fn new(core: Core<Eff, A>) -> Self {
Self {
core,
registry: Default::default(),
}
}
/// Receive an event from the shell.
///
/// The `event` is serialized and will be deserialized by the core before it's passed
/// to your app.
pub fn process_event<'de, D, S>(&self, event: D, requests_out: S)
where
for<'a> A::Event: Deserialize<'a>,
D: ::serde::de::Deserializer<'de> + 'de,
S: ::serde::ser::Serializer,
{
let mut erased_de = <dyn erased_serde::Deserializer>::erase(event);
self.process(
None,
&mut erased_de,
&mut <dyn erased_serde::Serializer>::erase(requests_out),
);
}
/// Receive a response to a capability request from the shell.
///
/// The `output` is serialized capability output. It will be deserialized by the core.
/// The `id` MUST match the `id` of the effect that triggered it, else the core will panic.
pub fn handle_response<'de, D, S>(&self, id: u32, response: D, requests_out: S)
where
for<'a> A::Event: Deserialize<'a>,
D: ::serde::de::Deserializer<'de>,
S: ::serde::ser::Serializer,
{
let mut erased_response = <dyn erased_serde::Deserializer>::erase(response);
self.process(
Some(EffectId(id)),
&mut erased_response,
&mut <dyn erased_serde::Serializer>::erase(requests_out),
);
}
fn process(
&self,
id: Option<EffectId>,
data: &mut dyn erased_serde::Deserializer,
requests_out: &mut dyn erased_serde::Serializer,
) where
A::Event: for<'a> Deserialize<'a>,
{
let effects = match id {
None => {
let shell_event =
erased_serde::deserialize(data).expect("Message deserialization failed.");
self.core.process_event(shell_event)
}
Some(id) => {
self.registry.resume(id, data).expect(
"Response could not be handled. The request did not expect a response.",
);
self.core.process()
}
};
let requests: Vec<_> = effects
.into_iter()
.map(|eff| self.registry.register(eff))
.collect();
requests
.erased_serialize(requests_out)
.expect("Request serialization failed.")
}
/// Get the current state of the app's view model (serialized).
pub fn view<S>(&self, ser: S)
where
S: ::serde::ser::Serializer,
{
self.core
.view()
.erased_serialize(&mut <dyn erased_serde::Serializer>::erase(ser))
.expect("View should serialize")
}
}