use std::collections::HashSet;
use std::num::NonZero;
use crate::fake::ProcessorBuilder;
use crate::{EfficiencyClass, MemoryRegionId, ProcessorId};
#[derive(Clone, Debug)]
pub(crate) struct ResolvedProcessor {
pub(crate) id: ProcessorId,
pub(crate) memory_region_id: MemoryRegionId,
pub(crate) efficiency_class: EfficiencyClass,
}
#[derive(Clone, Debug)]
pub struct HardwareBuilder {
pub(crate) processors: Vec<ProcessorBuilder>,
pub(crate) max_processor_time: Option<f64>,
from_counts: bool,
}
impl Default for HardwareBuilder {
fn default() -> Self {
Self::new()
}
}
impl HardwareBuilder {
#[must_use]
pub fn new() -> Self {
Self {
processors: Vec::new(),
max_processor_time: None,
from_counts: false,
}
}
#[must_use]
pub fn from_counts(
processor_count: NonZero<usize>,
memory_region_count: NonZero<usize>,
) -> Self {
let mut processors = Vec::with_capacity(processor_count.get());
for i in 0..processor_count.get() {
#[expect(
clippy::cast_possible_truncation,
reason = "unrealistic to have more than u32::MAX memory regions"
)]
#[expect(clippy::arithmetic_side_effects, reason = "modulo cannot overflow")]
let region = (i % memory_region_count.get()) as u32;
processors.push(ProcessorBuilder::new().memory_region(region));
}
Self {
processors,
max_processor_time: None,
from_counts: true,
}
}
#[must_use]
pub fn processor(mut self, proc: ProcessorBuilder) -> Self {
assert!(
!self.from_counts,
"cannot add individual processors to a builder created with from_counts() - \
use new() instead to add processors manually"
);
self.processors.push(proc);
self
}
#[must_use]
pub fn max_processor_time(mut self, time: f64) -> Self {
self.max_processor_time = Some(time);
self
}
pub(crate) fn build_processors(&self) -> Vec<ResolvedProcessor> {
let mut used_ids: HashSet<ProcessorId> = HashSet::new();
for p in &self.processors {
if let Some(id) = p.explicit_id {
assert!(
used_ids.insert(id),
"duplicate processor ID {id} - each processor must have a unique ID"
);
}
}
let mut next_auto_id: ProcessorId = 0;
let mut resolved = Vec::with_capacity(self.processors.len());
for p in &self.processors {
let id = match p.explicit_id {
Some(id) => id,
None => {
while used_ids.contains(&next_auto_id) {
next_auto_id = next_auto_id
.checked_add(1)
.expect("too many processors for automatic ID assignment");
}
let id = next_auto_id;
used_ids.insert(id);
next_auto_id = next_auto_id
.checked_add(1)
.expect("too many processors for automatic ID assignment");
id
}
};
resolved.push(ResolvedProcessor {
id,
memory_region_id: p.memory_region_id,
efficiency_class: p.efficiency_class,
});
}
resolved
}
pub(crate) fn build_max_processor_time(&self) -> Option<f64> {
self.max_processor_time
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
#[allow(clippy::indexing_slicing, reason = "test code, panics are acceptable")]
mod tests {
use std::panic::{RefUnwindSafe, UnwindSafe};
use new_zealand::nz;
use static_assertions::assert_impl_all;
use super::*;
assert_impl_all!(HardwareBuilder: UnwindSafe, RefUnwindSafe);
#[test]
fn default_equals_new() {
let default_builder = HardwareBuilder::default();
let new_builder = HardwareBuilder::new();
assert_eq!(
default_builder.processors.len(),
new_builder.processors.len()
);
assert_eq!(
default_builder.max_processor_time,
new_builder.max_processor_time
);
assert_eq!(default_builder.from_counts, new_builder.from_counts);
}
#[test]
fn from_counts_creates_correct_count() {
let builder = HardwareBuilder::from_counts(nz!(4), nz!(1));
assert_eq!(builder.processors.len(), 4);
for p in &builder.processors {
assert_eq!(p.explicit_id, None);
assert_eq!(p.memory_region_id, 0);
assert_eq!(p.efficiency_class, EfficiencyClass::Performance);
}
}
#[test]
fn from_counts_assigns_sequential_ids() {
let builder = HardwareBuilder::from_counts(nz!(4), nz!(1));
let resolved = builder.build_processors();
assert_eq!(resolved.len(), 4);
for (i, p) in resolved.iter().enumerate() {
#[expect(clippy::cast_possible_truncation, reason = "test data is small enough")]
let expected_id = i as ProcessorId;
assert_eq!(p.id, expected_id);
}
}
#[test]
fn from_counts_distributes_round_robin() {
let builder = HardwareBuilder::from_counts(nz!(6), nz!(2));
assert_eq!(builder.processors[0].memory_region_id, 0);
assert_eq!(builder.processors[1].memory_region_id, 1);
assert_eq!(builder.processors[2].memory_region_id, 0);
assert_eq!(builder.processors[3].memory_region_id, 1);
assert_eq!(builder.processors[4].memory_region_id, 0);
assert_eq!(builder.processors[5].memory_region_id, 1);
}
#[test]
#[should_panic]
fn from_counts_forbids_processor() {
let builder = HardwareBuilder::from_counts(nz!(4), nz!(1));
drop(builder.processor(ProcessorBuilder::new()));
}
#[test]
fn explicit_id_is_respected() {
let builder = HardwareBuilder::new().processor(ProcessorBuilder::new().id(42));
let resolved = builder.build_processors();
assert_eq!(resolved.len(), 1);
assert_eq!(resolved[0].id, 42);
}
#[test]
fn auto_ids_skip_explicit_ids() {
let builder = HardwareBuilder::new()
.processor(ProcessorBuilder::new().id(1))
.processor(ProcessorBuilder::new())
.processor(ProcessorBuilder::new().id(0))
.processor(ProcessorBuilder::new());
let resolved = builder.build_processors();
assert_eq!(resolved.len(), 4);
assert_eq!(resolved[0].id, 1); assert_eq!(resolved[1].id, 2); assert_eq!(resolved[2].id, 0); assert_eq!(resolved[3].id, 3); }
#[test]
fn mixed_auto_and_explicit_with_gaps() {
let builder = HardwareBuilder::new()
.processor(ProcessorBuilder::new().id(10))
.processor(ProcessorBuilder::new())
.processor(ProcessorBuilder::new());
let resolved = builder.build_processors();
assert_eq!(resolved.len(), 3);
assert_eq!(resolved[0].id, 10); assert_eq!(resolved[1].id, 0); assert_eq!(resolved[2].id, 1); }
#[test]
#[should_panic]
fn duplicate_explicit_ids_panics() {
let builder = HardwareBuilder::new()
.processor(ProcessorBuilder::new().id(5))
.processor(ProcessorBuilder::new().id(5));
drop(builder.build_processors());
}
}