- interner impls logically separate from API in orchid-base (default host interner still in base for testing) - error reporting, logging, and a variety of other features passed down via context in extension, not yet in host to maintain library-ish profile, should consider options - no global spawn mechanic, the host has a spawn function but extensions only get a stash for enqueuing async work in sync callbacks which is then explicitly, manually, and with strict order popped and awaited - still deadlocks nondeterministically for some ungodly reason
79 lines
2.1 KiB
Rust
79 lines
2.1 KiB
Rust
use std::error::Error;
|
|
use std::io;
|
|
use std::pin::{Pin, pin};
|
|
use std::sync::Arc;
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
use std::task::{Context, Poll, Wake};
|
|
|
|
use futures::{AsyncRead, AsyncReadExt, AsyncWrite};
|
|
use itertools::{Chunk, Itertools};
|
|
|
|
use crate::Encode;
|
|
|
|
pub async fn encode_enum<'a, W: AsyncWrite + ?Sized>(
|
|
mut write: Pin<&'a mut W>,
|
|
id: u8,
|
|
f: impl AsyncFnOnce(Pin<&'a mut W>) -> io::Result<()>,
|
|
) -> io::Result<()> {
|
|
id.encode(write.as_mut()).await?;
|
|
f(write).await
|
|
}
|
|
|
|
pub fn print_bytes(b: &[u8]) -> String {
|
|
(b.iter().map(|b| format!("{b:02x}")))
|
|
.chunks(4)
|
|
.into_iter()
|
|
.map(|mut c: Chunk<_>| c.join(" "))
|
|
.join(" ")
|
|
}
|
|
|
|
pub async fn read_exact<R: AsyncRead + ?Sized>(
|
|
mut read: Pin<&mut R>,
|
|
bytes: &'static [u8],
|
|
) -> io::Result<()> {
|
|
let mut data = vec![0u8; bytes.len()];
|
|
read.read_exact(&mut data).await?;
|
|
if data == bytes {
|
|
Ok(())
|
|
} else {
|
|
let msg =
|
|
format!("Wrong bytes!\nExpected: {}\nFound: {}", print_bytes(bytes), print_bytes(&data));
|
|
Err(io::Error::new(io::ErrorKind::InvalidData, msg))
|
|
}
|
|
}
|
|
|
|
pub fn enc_vec(enc: &impl Encode) -> Vec<u8> {
|
|
let mut vec = Vec::new();
|
|
enc.encode_vec(&mut vec);
|
|
vec
|
|
}
|
|
|
|
/// Raises a bool flag when called
|
|
struct FlagWaker(AtomicBool);
|
|
impl Wake for FlagWaker {
|
|
fn wake(self: Arc<Self>) { self.0.store(true, Ordering::Relaxed) }
|
|
}
|
|
|
|
pub fn spin_on<F: Future>(fut: F) -> F::Output {
|
|
let flag = AtomicBool::new(false);
|
|
let flag_waker = Arc::new(FlagWaker(flag));
|
|
let mut future = pin!(fut);
|
|
loop {
|
|
let waker = flag_waker.clone().into();
|
|
let mut ctx = Context::from_waker(&waker);
|
|
match future.as_mut().poll(&mut ctx) {
|
|
// ideally the future should return synchronously
|
|
Poll::Ready(res) => break res,
|
|
// poorly written futures may yield and immediately wake
|
|
Poll::Pending if flag_waker.0.load(Ordering::Relaxed) => (),
|
|
// there is no external event to wait for, this has to be a deadlock
|
|
Poll::Pending => panic!("Future inside spin_on cannot block"),
|
|
};
|
|
}
|
|
}
|
|
|
|
pub fn decode_err() -> io::Error { io::Error::new(io::ErrorKind::InvalidData, "Unexpected zero") }
|
|
pub fn decode_err_for(e: impl Error) -> io::Error {
|
|
io::Error::new(io::ErrorKind::InvalidData, e.to_string())
|
|
}
|