#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StageZone {
BackWall,
SideLeft,
SideRight,
Ceiling,
Floor,
TalentArea,
}
impl StageZone {
#[must_use]
pub fn is_display_surface(&self) -> bool {
!matches!(self, StageZone::TalentArea)
}
#[allow(clippy::cast_precision_loss)]
#[must_use]
pub fn coverage_fraction(&self) -> f32 {
match self {
StageZone::BackWall => 0.30,
StageZone::SideLeft => 0.15,
StageZone::SideRight => 0.15,
StageZone::Ceiling => 0.20,
StageZone::Floor => 0.10,
StageZone::TalentArea => 0.10,
}
}
}
impl std::fmt::Display for StageZone {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StageZone::BackWall => write!(f, "BackWall"),
StageZone::SideLeft => write!(f, "SideLeft"),
StageZone::SideRight => write!(f, "SideRight"),
StageZone::Ceiling => write!(f, "Ceiling"),
StageZone::Floor => write!(f, "Floor"),
StageZone::TalentArea => write!(f, "TalentArea"),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct ZoneDimensions {
pub width_m: f32,
pub height_m: f32,
pub depth_m: f32,
}
impl ZoneDimensions {
#[must_use]
pub fn new(width_m: f32, height_m: f32, depth_m: f32) -> Self {
Self {
width_m,
height_m,
depth_m,
}
}
#[allow(clippy::cast_precision_loss)]
#[must_use]
pub fn surface_area_m2(&self) -> f32 {
self.width_m * self.height_m
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ZoneState {
Online,
Standby,
Faulted,
}
impl std::fmt::Display for ZoneState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ZoneState::Online => write!(f, "Online"),
ZoneState::Standby => write!(f, "Standby"),
ZoneState::Faulted => write!(f, "Faulted"),
}
}
}
#[derive(Debug, Clone)]
pub struct StageLayout {
pub zone: StageZone,
pub dimensions: ZoneDimensions,
pub state: ZoneState,
pub pixel_pitch_mm: Option<f32>,
}
impl StageLayout {
#[must_use]
pub fn new(zone: StageZone, dimensions: ZoneDimensions, pixel_pitch_mm: Option<f32>) -> Self {
Self {
zone,
dimensions,
state: ZoneState::Standby,
pixel_pitch_mm,
}
}
pub fn bring_online(&mut self) {
if self.state != ZoneState::Faulted {
self.state = ZoneState::Online;
}
}
pub fn put_standby(&mut self) {
self.state = ZoneState::Standby;
}
pub fn fault(&mut self) {
self.state = ZoneState::Faulted;
}
#[must_use]
pub fn pixel_width(&self) -> Option<u32> {
self.pixel_pitch_mm.map(|pitch_mm| {
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let w = ((self.dimensions.width_m * 1000.0) / pitch_mm) as u32;
w
})
}
#[must_use]
pub fn pixel_height(&self) -> Option<u32> {
self.pixel_pitch_mm.map(|pitch_mm| {
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let h = ((self.dimensions.height_m * 1000.0) / pitch_mm) as u32;
h
})
}
#[must_use]
pub fn is_active(&self) -> bool {
self.state == ZoneState::Online
}
}
#[derive(Debug, Default)]
pub struct StageLayoutManager {
zones: Vec<StageLayout>,
}
impl StageLayoutManager {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add_zone(&mut self, layout: StageLayout) {
self.zones.push(layout);
}
#[must_use]
pub fn zones(&self) -> &[StageLayout] {
&self.zones
}
#[must_use]
pub fn find_zone(&self, zone: StageZone) -> Option<&StageLayout> {
self.zones.iter().find(|z| z.zone == zone)
}
pub fn find_zone_mut(&mut self, zone: StageZone) -> Option<&mut StageLayout> {
self.zones.iter_mut().find(|z| z.zone == zone)
}
pub fn bring_all_online(&mut self) {
for z in &mut self.zones {
if z.zone.is_display_surface() {
z.bring_online();
}
}
}
#[must_use]
pub fn online_count(&self) -> usize {
self.zones.iter().filter(|z| z.is_active()).count()
}
#[allow(clippy::cast_precision_loss)]
#[must_use]
pub fn total_online_area_m2(&self) -> f32 {
self.zones
.iter()
.filter(|z| z.is_active() && z.zone.is_display_surface())
.map(|z| z.dimensions.surface_area_m2())
.sum()
}
#[must_use]
pub fn faulted_count(&self) -> usize {
self.zones
.iter()
.filter(|z| z.state == ZoneState::Faulted)
.count()
}
#[must_use]
pub fn zone_count(&self) -> usize {
self.zones.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_back_wall() -> StageLayout {
StageLayout::new(
StageZone::BackWall,
ZoneDimensions::new(12.0, 4.5, 0.1),
Some(2.84),
)
}
fn make_talent_area() -> StageLayout {
StageLayout::new(
StageZone::TalentArea,
ZoneDimensions::new(8.0, 4.0, 6.0),
None,
)
}
#[test]
fn test_stage_zone_is_display_surface() {
assert!(StageZone::BackWall.is_display_surface());
assert!(StageZone::Ceiling.is_display_surface());
assert!(!StageZone::TalentArea.is_display_surface());
}
#[test]
fn test_stage_zone_coverage_fractions_sum() {
let zones = [
StageZone::BackWall,
StageZone::SideLeft,
StageZone::SideRight,
StageZone::Ceiling,
StageZone::Floor,
StageZone::TalentArea,
];
let total: f32 = zones.iter().map(super::StageZone::coverage_fraction).sum();
assert!((total - 1.0).abs() < f32::EPSILON * 10.0);
}
#[test]
fn test_stage_zone_display() {
assert_eq!(StageZone::BackWall.to_string(), "BackWall");
assert_eq!(StageZone::TalentArea.to_string(), "TalentArea");
assert_eq!(StageZone::SideLeft.to_string(), "SideLeft");
}
#[test]
fn test_zone_dimensions_surface_area() {
let d = ZoneDimensions::new(10.0, 4.0, 0.5);
assert!((d.surface_area_m2() - 40.0).abs() < 0.001);
}
#[test]
fn test_zone_state_display() {
assert_eq!(ZoneState::Online.to_string(), "Online");
assert_eq!(ZoneState::Standby.to_string(), "Standby");
assert_eq!(ZoneState::Faulted.to_string(), "Faulted");
}
#[test]
fn test_stage_layout_bring_online() {
let mut layout = make_back_wall();
assert_eq!(layout.state, ZoneState::Standby);
layout.bring_online();
assert_eq!(layout.state, ZoneState::Online);
assert!(layout.is_active());
}
#[test]
fn test_stage_layout_fault_prevents_online() {
let mut layout = make_back_wall();
layout.fault();
layout.bring_online(); assert_eq!(layout.state, ZoneState::Faulted);
}
#[test]
fn test_stage_layout_put_standby() {
let mut layout = make_back_wall();
layout.bring_online();
layout.put_standby();
assert_eq!(layout.state, ZoneState::Standby);
}
#[test]
fn test_stage_layout_pixel_dimensions() {
let layout = make_back_wall();
let pw = layout.pixel_width().expect("should succeed in test");
assert!(pw > 4000 && pw < 5000);
let ph = layout.pixel_height().expect("should succeed in test");
assert!(ph > 1000 && ph < 2000);
}
#[test]
fn test_stage_layout_no_pixel_pitch() {
let layout = make_talent_area();
assert!(layout.pixel_width().is_none());
assert!(layout.pixel_height().is_none());
}
#[test]
fn test_manager_bring_all_online() {
let mut mgr = StageLayoutManager::new();
mgr.add_zone(make_back_wall());
mgr.add_zone(make_talent_area());
mgr.bring_all_online();
assert_eq!(mgr.online_count(), 1);
}
#[test]
fn test_manager_total_online_area() {
let mut mgr = StageLayoutManager::new();
mgr.add_zone(make_back_wall());
mgr.bring_all_online();
let area = mgr.total_online_area_m2();
assert!((area - 54.0).abs() < 0.001);
}
#[test]
fn test_manager_faulted_count() {
let mut mgr = StageLayoutManager::new();
let mut zone = make_back_wall();
zone.fault();
mgr.add_zone(zone);
assert_eq!(mgr.faulted_count(), 1);
}
#[test]
fn test_manager_find_zone_mut() {
let mut mgr = StageLayoutManager::new();
mgr.add_zone(make_back_wall());
let z = mgr
.find_zone_mut(StageZone::BackWall)
.expect("should succeed in test");
z.bring_online();
assert!(mgr
.find_zone(StageZone::BackWall)
.expect("should succeed in test")
.is_active());
}
#[test]
fn test_manager_zone_count() {
let mut mgr = StageLayoutManager::new();
assert_eq!(mgr.zone_count(), 0);
mgr.add_zone(make_back_wall());
assert_eq!(mgr.zone_count(), 1);
}
}