corevm-engine 0.1.28

CoreVM engine that drives program execution either on the builder or CoreVM service side
Documentation
use crate::{Engine, OuterVm, TimeLimitReached};
use corevm_host::{AudioMode, VideoMode};
use jam_types::slot_period_sec;
use log::debug;

impl<O: OuterVm> Engine<O> {
	pub(crate) fn advance_time(&mut self) -> Result<(), TimeLimitReached> {
		advance_time(
			&mut self.timestamp,
			self.work_output.vm_state.video.as_ref(),
			self.num_video_frames,
			self.work_output.vm_state.audio.as_ref(),
			self.num_audio_bytes,
		)
	}
}

fn max_video_frames_per_slot(video: Option<&VideoMode>) -> u64 {
	video
		.map(|video| (video.refresh_rate.get() as u64).saturating_mul(slot_period_sec()))
		.unwrap_or(0)
}

fn max_audio_bytes_per_slot(audio: Option<&AudioMode>) -> u64 {
	audio
		.map(|audio| audio.bytes_per_second().get().saturating_mul(slot_period_sec()))
		.unwrap_or(0)
}

pub(crate) trait VideoModeExt {
	/// The number of video frames per time-slot.
	fn frames_per_slot(self) -> u64;
}

impl VideoModeExt for VideoMode {
	fn frames_per_slot(self) -> u64 {
		(u64::from(self.refresh_rate.get())).saturating_mul(slot_period_sec())
	}
}

pub(crate) trait AudioModeExt {
	/// The number of audio samples per time-slot, in bytes.
	fn bytes_per_slot(self) -> u64;
}

impl AudioModeExt for AudioMode {
	fn bytes_per_slot(self) -> u64 {
		self.bytes_per_second().get().saturating_mul(slot_period_sec())
	}
}

fn advance_time(
	timestamp: &mut u64,
	video: Option<&VideoMode>,
	video_frames: u64,
	audio: Option<&AudioMode>,
	audio_bytes: u64,
) -> Result<(), TimeLimitReached> {
	let max_video_frames = max_video_frames_per_slot(video);
	let max_audio_bytes = max_audio_bytes_per_slot(audio);
	let video_enabled = max_video_frames != 0;
	let video_limit_reached = video_enabled && video_frames >= max_video_frames;
	let audio_enabled = max_audio_bytes != 0;
	let audio_limit_reached = audio_enabled && audio_bytes >= max_audio_bytes;
	// Advance the current timestamp.
	let slot_in_ms = slot_period_sec() * 1_000_u64;
	for (enabled, current, max) in [
		(video_enabled, video_frames, max_video_frames),
		(audio_enabled, audio_bytes, max_audio_bytes),
	] {
		if !enabled {
			continue;
		}
		if current >= max {
			*timestamp = slot_in_ms;
			break;
		}
		let next_timestamp = current * slot_in_ms / max;
		if next_timestamp > *timestamp {
			*timestamp = next_timestamp;
		}
	}
	let result = match (video_limit_reached, audio_limit_reached) {
		(true, true) => Err(TimeLimitReached),
		(true, false) if !audio_enabled => Err(TimeLimitReached),
		(false, true) if !video_enabled => Err(TimeLimitReached),
		_ => Ok(()),
	};
	if result.is_err() {
		debug!("Video frame/audio samples limit reached: {video_frames} video frames, {audio_bytes} B of audio samples");
	}
	result
}