//! Framed requests between an app and its engine worker.
mod relay;
pub use relay::{Relay, relay};
use std::io::{self, Read, Write};
use std::process::Command;
/// A child that opens no console window on Windows.
pub fn windowless(command: &mut Command) -> &mut Command {
#[cfg(windows)]
{
use std::os::windows::process::CommandExt as _;
// CREATE_NO_WINDOW
command.creation_flags(0x0800_0000);
}
command
}
/// A frame: u32 length, u8 tag, u64 request id, payload.
pub const MAX_FRAME: usize = 64 * 1024 * 1024;
/// Frames no request asked for, such as pushed events.
pub const UNASKED: u64 = 0;
/// Writes one frame and flushes; replies carry the request's id.
pub fn write_frame(out: &mut impl Write, tag: u8, id: u64, payload: &[u8]) -> io::Result<()> {
// The reader's cap, checked before a byte goes out.
if payload.len() > MAX_FRAME {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("a {} byte frame is over the {MAX_FRAME} byte cap", payload.len()),
));
}
let len = payload.len() as u32;
out.write_all(&len.to_le_bytes())?;
out.write_all(&[tag])?;
out.write_all(&id.to_le_bytes())?;
out.write_all(payload)?;
out.flush()
}
/// A reply with another request's id means the pairing broke.
pub fn check_reply(asked: u64, got: u64) -> io::Result<()> {
match asked == got {
true => Ok(()),
false => Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("the worker answered request {got}, not {asked}"),
)),
}
}
/// Reads (tag, id, payload); None at a clean stream end.
pub fn read_frame(input: &mut impl Read) -> io::Result