Non-Synchronizing Idioms
Scalar Pattern
A scalar non-synchronizing call has the following characterisics:
- Up to 4
u32
-sized arguments - Caller does not block
- Callee does not return any result
- No guarantee of synchronization between caller and callee
- Side effects may happen at an arbitrary time later
- Messages are guaranteed to arrive in order
// api.rs
pub(crate) enum Opcode {
Lights,
// ... and other ops
}
// lib.rs:
impl MyService {
// ... new(), etc.
/// Tell the main loop to set the state of lights. When this call exits, all we know is
/// a message is "en route" to the main loop, but we can't guarantee anything has happened.
pub fn set_lights(&self, state: bool) -> Result<(), xous::Error> {
send_message(self.conn,
Message::new_scalar(Opcode::Lights.to_usize().unwrap()),
if state {1} else {0},
0,
0,
0
)
).map(|_|())
}
}
// main.rs:
fn xmain() -> ! {
// ... preamble
loop {
let msg = xous::receive_message(sid).unwrap();
match FromPrimitive::from_usize(msg.body.id()) {
/// This will get processed whenever the server gets scheduled, which has no strict
/// relationship to the caller's state. However, messages are guaranteed
/// to be processed in-order.
Some(Opcode::Lights) => xous::msg_scalar_unpack!(msg, state, _, _, _, {
if state == 1 {
turn_lights_on();
} else {
turn_lights_off();
}
}),
// .. other match statements
}
}
// ... postamble
}
Memory Pattern
A memory non-synchronizing call has the following characterisics:
- Messages are sent in blocks rounded up to the nearest 4096-byte page size
- Caller does not block
- Callee does not return any result
- No guarantee of synchronization between caller and callee
- Side effects may happen at an arbitrary time later
- Messages are guaranteed to arrive in order
// api.rs
pub(crate) enum Opcode {
// use `rkyv` to serialize a memory message and send
PushDataRkyv,
// example of explicit serialization
PushDataExplicit,
// ... and other ops
}
/// `rkyv` can be used as a convenience method to serialize data in complex structures.
/// Almost any type can be contained in the structure (enums, other structures), but the
/// type must also `derive` the `rkyv` Archive, Serialize, and Deserialize traits.
/// Thus one cannot simply seralize a `std::string::String`; it must be transcribed into
/// a `xous_ipc::String::<N>` type which has a defined allocation size of `N`.
#[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
pub struct CompoundData {
pub data: [u8; 1000],
pub len: u16,
pub description: xous_ipc::String::<128>,
}
// lib.rs:
impl MyService {
// ... new(), etc.
/// Send some `data` to the server. It'll get there when it gets there.
/// This example uses `rkyv` to serialize data into a compound structure.
pub fn push_data_rkyv(&self, data: &[u8], desc: &str) -> Result<(), xous::Error> {
let mut rec = CompoundData {
data: [0u8; 1000],
len: 0,
description: xous_ipc::String::new(),
};
if data.len() > rec.data.len() {
return Err(xous::Error::OutOfMemory);
}
for (&s, d) in data.iter().zip(rec.data.iter_mut()) {
*d = s;
}
data.len = data.len() as u16;
rec.description.append(desc).ok(); // overflows are silently truncated
// now consume `rec` and turn it into a Xous::Buffer, which can then be mapped into the
// callee's memory space by `send`
let buf = Buffer::into_buf(rec).or(Err(xous::Error::InternalError))?;
buf.send(self.conn, Opcode::PushDataRkyv.to_u32().unwrap()).map(|_| ())
}
}
// main.rs:
fn xmain() -> ! {
// ... preamble
let mut storage = Vec::<CompoundData>::new();
let mut raw_data = [0u8; 32];
loop {
let msg = xous::receive_message(sid).unwrap();
match FromPrimitive::from_usize(msg.body.id()) {
/// This will get processed whenever the server gets scheduled, which has no strict
/// relationship to the caller's state. However, messages are guaranteed
/// to be processed in-order.
Some(Opcode::PushDataRkyv) => {
let buffer = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) };
// `.to_original()` automatically makes a copy of the data into my process space.
// This adds overhead and time, but your original types are restored.
// `.as_flat()` will use the data directly out of the messages' memory space without copying it,
// but it introduces some type complexity. We don't give an example here, but you may find
// one in the TRNG's `FillTrng` implementation, where we avoid making two copies of the
// data for a more performant implementation.
let data = buffer.to_original::<PushDataRkyv, _>().unwrap();
storage.push(data);
}
// .. other match statements
}
}
// ... postamble
}