use alloc::vec::Vec;
use codec::{Encode, Output as _};
use corevm_host::{ConsoleChunks, OutputStream};
use jam_types::SEGMENT_LEN;
#[derive(Default)]
pub struct OutputBuffers {
console_buffers: [ConsoleChunks; 2],
buffers: [Vec<u8>; 2],
}
impl OutputBuffers {
#[must_use]
pub fn pre_allocate(&mut self, time_offset: u64, i: OutputStream, len: usize) -> &mut [u8] {
if len == 0 {
return &mut [];
}
match i {
OutputStream::Stdout | OutputStream::Stderr =>
self.console_buffers[i as usize].pre_allocate(time_offset, len),
_ => {
let buf = &mut self.buffers[i as usize - 2];
let offset = buf.len();
buf.resize(offset + len, 0_u8);
&mut buf[offset..]
},
}
}
pub fn append(&mut self, time_offset: u64, i: OutputStream, slice: &[u8]) {
if slice.is_empty() {
return;
}
match i {
OutputStream::Stdout | OutputStream::Stderr =>
self.console_buffers[i as usize].append(time_offset, slice.into()),
_ => self.buffers[i as usize - 2].extend_from_slice(slice),
}
}
pub fn export_segments<F, E>(&mut self, export: F) -> Result<(), E>
where
F: FnMut(&RawSegment) -> Result<(), E>,
{
let mut output = SegmentedOutput::new(export);
for chunks in self.console_buffers.iter() {
chunks.encode_to(&mut output);
if let Some(e) = output.error {
return Err(e);
}
}
for buf in self.buffers.iter() {
output.write(buf);
if let Some(e) = output.error {
return Err(e);
}
}
output.finish()?;
Ok(())
}
pub fn stream_len(&self) -> [u32; OutputStream::COUNT] {
[
self.console_buffers[0].encoded_size() as u32,
self.console_buffers[1].encoded_size() as u32,
self.buffers[0].len() as u32,
self.buffers[1].len() as u32,
]
}
pub fn segment_count(&self) -> usize {
self.total_len().div_ceil(SEGMENT_LEN)
}
pub fn total_len(&self) -> usize {
self.stream_len().iter().sum::<u32>() as usize
}
fn buffer_len(&self, i: OutputStream) -> usize {
use OutputStream::*;
match i {
Stdout | Stderr => self.console_buffers[i as usize].encoded_size(),
Video | Audio => self.buffers[i as usize - 2].len(),
}
}
fn buffer_len_after(&self, time_offset: u64, i: OutputStream, len: usize) -> Option<usize> {
use OutputStream::*;
match i {
Stdout | Stderr =>
self.console_buffers[i as usize].encoded_size_after(time_offset, len),
Video | Audio => self.buffers[i as usize - 2].len().checked_add(len),
}
}
pub fn new_segments_after(
&self,
time_offset: u64,
i: OutputStream,
len: usize,
) -> Option<usize> {
let old_len = self.buffer_len(i);
let new_len = self.buffer_len_after(time_offset, i, len)?;
let old_total_len = self.total_len();
let old_segments = old_total_len.div_ceil(SEGMENT_LEN);
let new_total_len = (old_total_len - old_len).checked_add(new_len)?;
let new_segments = new_total_len.div_ceil(SEGMENT_LEN);
Some(new_segments - old_segments)
}
}
struct SegmentedOutput<E, F: FnMut(&RawSegment) -> Result<(), E>> {
segment: RawSegment,
offset: usize,
export: F,
error: Option<E>,
}
impl<E, F: FnMut(&RawSegment) -> Result<(), E>> SegmentedOutput<E, F> {
fn new(export: F) -> Self {
Self { segment: [0; SEGMENT_LEN], offset: 0, export, error: None }
}
fn finish(&mut self) -> Result<(), E> {
if self.offset == 0 {
return Ok(());
}
for i in self.offset..SEGMENT_LEN {
self.segment[i] = 0;
}
(self.export)(&self.segment)?;
self.offset = 0;
Ok(())
}
}
impl<E, F: FnMut(&RawSegment) -> Result<(), E>> codec::Output for SegmentedOutput<E, F> {
fn write(&mut self, mut bytes: &[u8]) {
if self.error.is_some() {
return;
}
while !bytes.is_empty() {
let n = (SEGMENT_LEN - self.offset).min(bytes.len());
self.segment[self.offset..self.offset + n].copy_from_slice(&bytes[..n]);
self.offset += n;
bytes = &bytes[n..];
if self.offset == SEGMENT_LEN {
if let Err(e) = (self.export)(&self.segment) {
self.error = Some(e);
break;
}
self.offset = 0;
}
}
}
}
type RawSegment = [u8; SEGMENT_LEN];
#[cfg(test)]
mod tests {
use super::*;
use core::convert::Infallible;
use corevm_host::ConsoleChunk;
use jam_types::SegmentBytes;
use rand::{seq::IndexedRandom, Rng, RngCore};
#[test]
fn output_buffers_work() {
let mut rng = rand::rng();
for _ in 0..1000 {
let mut guest_buffers = OutputBuffers::default();
for stream in OutputStream::ALL {
let num_bytes: u32 = rng.random_range(0..=100);
let slice = guest_buffers.pre_allocate(0, stream, num_bytes as usize);
rng.fill_bytes(slice);
}
let stream_len = guest_buffers.stream_len();
let mut segments: Vec<SegmentBytes> = Vec::new();
guest_buffers
.export_segments(|s| {
segments.push(s.into());
Ok::<(), Infallible>(())
})
.unwrap();
let host_buffers =
corevm_host::OutputBuffers::from_segments(&segments[..], &stream_len).unwrap();
for i in [OutputStream::Video, OutputStream::Audio] {
let guest_bytes = &guest_buffers.buffers[i as usize - 2];
assert_eq!(guest_bytes, host_buffers.get_encoded_buf(i));
}
for i in [OutputStream::Stdout, OutputStream::Stderr] {
let guest_bytes = &guest_buffers.console_buffers[i as usize];
assert_eq!(
guest_bytes
.iter()
.map(|ConsoleChunk { time_offset, buf }| (time_offset, buf))
.collect::<Vec<_>>(),
host_buffers
.get_console_buf(i)
.iter()
.map(|ConsoleChunk { time_offset, buf }| (time_offset, buf))
.collect::<Vec<_>>(),
);
}
}
}
#[test]
fn new_segments_after_works() {
let mut rng = rand::rng();
for _ in 0..1000 {
let num_appends = rng.random_range(0..=20);
let mut guest_buffers = OutputBuffers::default();
for _ in 0..num_appends {
let stream = *OutputStream::ALL.choose(&mut rng).unwrap();
let num_bytes: usize = rng.random_range(0..=100);
let segment_count = guest_buffers.segment_count();
let expected_segment_count =
segment_count + guest_buffers.new_segments_after(0, stream, num_bytes).unwrap();
let slice = guest_buffers.pre_allocate(0, stream, num_bytes);
rng.fill_bytes(slice);
let mut actual_segment_count = 0;
guest_buffers
.export_segments(|_| {
actual_segment_count += 1;
Ok::<(), Infallible>(())
})
.unwrap();
assert_eq!(
expected_segment_count, actual_segment_count,
"expected segment count = {expected_segment_count}, \
actual segment count = {actual_segment_count}"
);
}
}
}
}