rusty_emu/src/component.rs
2025-09-22 18:40:13 +02:00

255 lines
No EOL
7.7 KiB
Rust

use std::collections::HashMap;
use std::sync::{Arc, Mutex, atomic::{AtomicBool, Ordering}};
use std::thread;
use std::time::Duration;
use crate::pin::Pin;
pub trait Component {
fn name(&self) -> String;
fn pins(&self) -> &HashMap<String, Arc<Mutex<Pin>>>;
fn get_pin(&self, name: &str) -> Result<Arc<Mutex<Pin>>, String>;
fn update(&mut self);
fn run(&mut self);
fn stop(&mut self);
// Helper method to check if component is running
fn is_running(&self) -> bool;
}
pub struct BaseComponent {
name: String,
pins: HashMap<String, Arc<Mutex<Pin>>>,
running: AtomicBool,
}
impl BaseComponent {
pub fn new(name: String, pins: HashMap<String, Arc<Mutex<Pin>>>) -> Self {
BaseComponent {
name,
pins,
running: AtomicBool::new(false),
}
}
pub fn get_name(&self) -> &str {
&self.name
}
pub fn is_running(&self) -> bool {
self.running.load(Ordering::SeqCst)
}
pub fn set_running(&self, running: bool) {
self.running.store(running, Ordering::SeqCst);
}
pub fn create_pin_map(pin_names: &[&str], component_name: &str) -> HashMap<String, Arc<Mutex<Pin>>> {
let mut pins = HashMap::new();
for pin_name in pin_names {
pins.insert(
pin_name.to_string(),
Arc::new(Mutex::new(Pin::new(format!("{}_{}", component_name, pin_name))))
);
}
pins
}
pub fn add_pin(&mut self, name: String, pin: Arc<Mutex<Pin>>) {
self.pins.insert(name, pin);
}
pub fn remove_pin(&mut self, name: &str) -> Option<Arc<Mutex<Pin>>> {
self.pins.remove(name)
}
// Helper method to get a mutable reference to pins (for components that need to modify pins)
pub fn pins_mut(&mut self) -> &mut HashMap<String, Arc<Mutex<Pin>>> {
&mut self.pins
}
}
// Implement Clone manually without AtomicBool
impl Clone for BaseComponent {
fn clone(&self) -> Self {
// Create new pins with the same names but new Pin instances
let mut new_pins = HashMap::new();
for (name, pin) in &self.pins {
let pin_guard = pin.lock().unwrap();
new_pins.insert(
name.clone(),
Arc::new(Mutex::new(Pin::new(pin_guard.name().to_string())))
);
}
BaseComponent {
name: self.name.clone(),
pins: new_pins,
running: AtomicBool::new(false), // Always start as not running when cloned
}
}
}
// Default implementation for Component trait
impl Component for BaseComponent {
fn name(&self) -> String {
self.name.clone()
}
fn pins(&self) -> &HashMap<String, Arc<Mutex<Pin>>> {
&self.pins
}
fn get_pin(&self, name: &str) -> Result<Arc<Mutex<Pin>>, String> {
self.pins.get(name)
.cloned()
.ok_or_else(|| format!("Pin {} not found on component {}", name, self.name))
}
fn update(&mut self) {
// Base implementation does nothing - components should override this
// This is where pin state changes would be processed
}
fn run(&mut self) {
self.set_running(true);
// Simple run loop - components should override this for their specific behavior
while self.is_running() {
self.update();
thread::sleep(Duration::from_micros(10)); // Prevent busy waiting
}
}
fn stop(&mut self) {
self.set_running(false);
}
fn is_running(&self) -> bool {
self.running.load(Ordering::SeqCst)
}
}
// Helper function to create component pins easily
pub fn create_component_pins(component_name: &str, pin_definitions: &[(&str, &str)]) -> HashMap<String, Arc<Mutex<Pin>>> {
let mut pins = HashMap::new();
for (pin_name, pin_type) in pin_definitions {
let full_pin_name = format!("{}_{}", component_name, pin_name);
let mut pin = Pin::new(full_pin_name);
// Set initial state based on pin type
match *pin_type {
"input" | "in" => {
// Input pins start as HighZ (waiting for input)
pin.set_driver(Some("internal".to_string()), crate::pin::PinValue::HighZ);
}
"output" | "out" => {
// Output pins start as HighZ (tri-state)
pin.set_driver(Some("internal".to_string()), crate::pin::PinValue::HighZ);
}
"power" => {
// Power pins start high
pin.set_driver(Some("internal".to_string()), crate::pin::PinValue::High);
}
"ground" => {
// Ground pins start low
pin.set_driver(Some("internal".to_string()), crate::pin::PinValue::Low);
}
_ => {
// Default to HighZ
pin.set_driver(Some("internal".to_string()), crate::pin::PinValue::HighZ);
}
}
pins.insert(pin_name.to_string(), Arc::new(Mutex::new(pin)));
}
pins
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_base_component_creation() {
let pins = BaseComponent::create_pin_map(&["A0", "A1", "D0", "D1"], "TEST");
let component = BaseComponent::new("TEST_COMP".to_string(), pins);
assert_eq!(component.name(), "TEST_COMP");
assert!(!component.is_running());
}
#[test]
fn test_base_component_pin_management() {
let pins = BaseComponent::create_pin_map(&["A0", "D0"], "TEST");
let mut component = BaseComponent::new("TEST_COMP".to_string(), pins);
// Test getting existing pin
assert!(component.get_pin("A0").is_ok());
// Test getting non-existent pin
assert!(component.get_pin("NON_EXISTENT").is_err());
// Test adding new pin
let new_pin = Arc::new(Mutex::new(Pin::new("TEST_NEW".to_string())));
component.add_pin("NEW_PIN".to_string(), new_pin);
assert!(component.get_pin("NEW_PIN").is_ok());
// Test removing pin
let removed = component.remove_pin("A0");
assert!(removed.is_some());
assert!(component.get_pin("A0").is_err());
}
#[test]
fn test_base_component_lifecycle() {
let pins = BaseComponent::create_pin_map(&["TEST"], "TEST");
let mut component = BaseComponent::new("TEST_COMP".to_string(), pins);
assert!(!component.is_running());
// Note: In real usage, run() would be called in a separate thread
component.set_running(true);
assert!(component.is_running());
component.stop();
assert!(!component.is_running());
}
#[test]
fn test_base_component_clone() {
let pins = BaseComponent::create_pin_map(&["A0", "D0"], "ORIG");
let original = BaseComponent::new("ORIGINAL".to_string(), pins);
original.set_running(true);
let cloned = original.clone();
// Cloned component should have same name but not running state
assert_eq!(cloned.name(), "ORIGINAL");
assert!(!cloned.is_running()); // Clone should not be running
// Pins should exist but be different instances
assert!(cloned.get_pin("A0").is_ok());
assert!(cloned.get_pin("D0").is_ok());
}
#[test]
fn test_create_component_pins() {
let pin_definitions = vec![
("A0", "input"),
("D0", "output"),
("VCC", "power"),
("GND", "ground"),
];
let pins = create_component_pins("TEST", &pin_definitions);
assert_eq!(pins.len(), 4);
assert!(pins.contains_key("A0"));
assert!(pins.contains_key("D0"));
assert!(pins.contains_key("VCC"));
assert!(pins.contains_key("GND"));
}
}