use std::time::Duration;
use shadow_shim_helper_rs::{emulated_time::EmulatedTime, simulation_time::SimulationTime};
pub struct Cpu {
simulated_frequency: u64,
native_frequency: u64,
threshold: Option<SimulationTime>,
precision: Option<SimulationTime>,
now: EmulatedTime,
time_cpu_available: EmulatedTime,
}
impl Cpu {
pub fn new(
simulated_frequency: u64,
native_frequency: u64,
threshold: Option<SimulationTime>,
precision: Option<SimulationTime>,
) -> Self {
if let Some(precision) = precision {
assert!(precision > SimulationTime::ZERO)
}
Self {
simulated_frequency,
native_frequency,
threshold,
precision,
now: EmulatedTime::MIN,
time_cpu_available: EmulatedTime::MIN,
}
}
pub fn update_time(&mut self, now: EmulatedTime) {
self.now = now;
}
pub fn add_delay(&mut self, native_delay: Duration) {
let cycles = native_delay
.as_nanos()
.checked_mul(self.native_frequency as u128)
.unwrap();
let simulated_delay_nanos = cycles / (self.simulated_frequency as u128);
let mut adjusted_delay =
SimulationTime::from_nanos(simulated_delay_nanos.try_into().unwrap());
if let Some(precision) = self.precision {
let remainder = adjusted_delay % precision;
adjusted_delay -= remainder;
let half_precision = precision / 2;
if remainder >= half_precision {
adjusted_delay += precision;
}
}
self.time_cpu_available += adjusted_delay;
}
pub fn delay(&self) -> SimulationTime {
let Some(threshold) = self.threshold else {
return SimulationTime::ZERO;
};
let Some(built_up_delay) = self.time_cpu_available.checked_duration_since(&self.now) else {
return SimulationTime::ZERO;
};
if built_up_delay > threshold {
built_up_delay
} else {
SimulationTime::ZERO
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const MHZ: u64 = 1_000_000;
#[test]
fn no_threshold_never_delays() {
let mut cpu = Cpu::new(1000 * MHZ, 1000 * MHZ, None, None);
assert_eq!(cpu.delay(), SimulationTime::ZERO);
cpu.add_delay(Duration::from_secs(1));
assert_eq!(cpu.delay(), SimulationTime::ZERO);
}
#[test]
fn basic_delay() {
let mut cpu = Cpu::new(
1000 * MHZ,
1000 * MHZ,
Some(SimulationTime::NANOSECOND),
None,
);
assert_eq!(cpu.delay(), SimulationTime::ZERO);
cpu.update_time(EmulatedTime::UNIX_EPOCH);
cpu.add_delay(Duration::from_secs(1));
assert_eq!(cpu.delay(), SimulationTime::SECOND);
cpu.update_time(EmulatedTime::UNIX_EPOCH + SimulationTime::from_millis(100));
assert_eq!(cpu.delay(), SimulationTime::from_millis(900));
cpu.update_time(EmulatedTime::UNIX_EPOCH + SimulationTime::from_secs(1));
assert_eq!(cpu.delay(), SimulationTime::ZERO);
cpu.update_time(EmulatedTime::UNIX_EPOCH + SimulationTime::from_secs(2));
assert_eq!(cpu.delay(), SimulationTime::ZERO);
}
#[test]
fn no_overflow() {
let mut cpu = Cpu::new(
1_000_000 * MHZ,
1_000_000 * MHZ,
Some(SimulationTime::NANOSECOND),
None,
);
cpu.add_delay(Duration::from_secs(3600));
assert_eq!(cpu.delay(), SimulationTime::from_secs(3600));
}
#[test]
fn faster_native() {
let mut cpu = Cpu::new(
1000 * MHZ,
1100 * MHZ,
Some(SimulationTime::NANOSECOND),
None,
);
assert_eq!(cpu.delay(), SimulationTime::ZERO);
cpu.add_delay(Duration::from_millis(1000));
assert_eq!(cpu.delay(), SimulationTime::from_millis(1100));
}
#[test]
fn faster_simulated() {
let mut cpu = Cpu::new(
1100 * MHZ,
1000 * MHZ,
Some(SimulationTime::NANOSECOND),
None,
);
assert_eq!(cpu.delay(), SimulationTime::ZERO);
cpu.add_delay(Duration::from_millis(1100));
assert_eq!(cpu.delay(), SimulationTime::from_millis(1000));
}
#[test]
fn thresholded() {
let threshold = SimulationTime::from_millis(100);
let mut cpu = Cpu::new(1000 * MHZ, 1000 * MHZ, Some(threshold), None);
assert_eq!(cpu.delay(), SimulationTime::ZERO);
cpu.add_delay(Duration::from_millis(1));
assert_eq!(cpu.delay(), SimulationTime::ZERO);
cpu.add_delay(Duration::from_millis(100));
assert_eq!(cpu.delay(), SimulationTime::from_millis(101));
}
#[test]
fn round_lt_half_precision() {
let precision = SimulationTime::from_millis(100);
let mut cpu = Cpu::new(
1000 * MHZ,
1000 * MHZ,
Some(SimulationTime::NANOSECOND),
Some(precision),
);
cpu.add_delay(Duration::from_millis(149));
assert_eq!(cpu.delay(), SimulationTime::from_millis(100));
}
#[test]
fn round_half_precision() {
let precision = SimulationTime::from_millis(100);
let mut cpu = Cpu::new(
1000 * MHZ,
1000 * MHZ,
Some(SimulationTime::NANOSECOND),
Some(precision),
);
cpu.add_delay(Duration::from_millis(150));
assert_eq!(cpu.delay(), SimulationTime::from_millis(200));
}
#[test]
fn round_gt_half_precision() {
let precision = SimulationTime::from_millis(100);
let mut cpu = Cpu::new(
1000 * MHZ,
1000 * MHZ,
Some(SimulationTime::NANOSECOND),
Some(precision),
);
cpu.add_delay(Duration::from_millis(151));
assert_eq!(cpu.delay(), SimulationTime::from_millis(200));
}
}