radiate-ui 1.2.20

A Rust library for genetic algorithms and artificial evolution.
Documentation
use crate::app::{App, InputEvent};
use color_eyre::{Result, eyre::Context};
use radiate_engines::{
    Chromosome, Engine, EngineIterator, Generation, GeneticEngine, error::RadiateResult,
    sync::ArcExt,
};
use std::{
    sync::{
        Arc,
        atomic::{AtomicBool, Ordering},
        mpsc,
    },
    time::Duration,
};

const KEY_REPEAT_DELAY: Duration = Duration::from_millis(100);

pub struct UiRuntime<C, T>
where
    C: Chromosome,
    T: Clone + Send + Sync + 'static,
{
    inner: GeneticEngine<C, T>,
    dispatcher: Arc<mpsc::Sender<InputEvent<C>>>,
    stop_flag: Arc<AtomicBool>,
    app_thread: Option<std::thread::JoinHandle<Result<()>>>,
    key_thread: Option<std::thread::JoinHandle<Result<()>>>,
}

impl<C, T> UiRuntime<C, T>
where
    C: Chromosome + Clone + 'static,
    T: Clone + Send + Sync + 'static,
{
    pub fn new(inner: GeneticEngine<C, T>, render_interval: Duration) -> Self {
        let app = App::new(render_interval);
        let (dispatch_one, dispatch_two) = app.dispatcher().into_pair();
        let (stop_one, stop_two) = Arc::pair(AtomicBool::new(false));

        let app_thread = std::thread::spawn(move || {
            let terminal = ratatui::init();
            app.run(terminal)?;
            ratatui::restore();
            Ok(())
        });

        let key_thread = std::thread::spawn(move || {
            while !stop_two.load(Ordering::Relaxed) {
                if crossterm::event::poll(KEY_REPEAT_DELAY)? {
                    let event = crossterm::event::read()?;
                    dispatch_two
                        .send(InputEvent::Crossterm(event))
                        .context("Failed to send Crossterm event")?;
                }
            }

            Ok(())
        });

        Self {
            inner,
            stop_flag: stop_one,
            dispatcher: dispatch_one,
            app_thread: Some(app_thread),
            key_thread: Some(key_thread),
        }
    }

    pub fn iter(self) -> impl Iterator<Item = Generation<C, T>> {
        EngineIterator::new(self)
    }
}

impl<C, T> Engine for UiRuntime<C, T>
where
    C: Chromosome + Clone,
    T: Clone + Send + Sync + 'static,
{
    type Epoch = Generation<C, T>;

    #[inline]
    fn next(&mut self) -> RadiateResult<Self::Epoch> {
        let current = self.inner.next()?;

        match current.index() {
            1 => self
                .dispatcher
                .send(InputEvent::EngineStart(current.objective().clone()))
                .unwrap(),
            _ => self
                .dispatcher
                .send(InputEvent::EpochComplete(
                    current.index(),
                    current.metrics().clone(),
                    current.score().clone(),
                    current.front().cloned(),
                ))
                .unwrap(),
        }

        Ok(current)
    }
}

impl<C, T> Drop for UiRuntime<C, T>
where
    C: Chromosome,
    T: Clone + Send + Sync + 'static,
{
    fn drop(&mut self) {
        self.dispatcher.send(InputEvent::EngineStop).unwrap();

        if let Some(event_listener) = self.app_thread.take() {
            if let Err(e) = event_listener.join() {
                eprintln!("Error joining app thread: {:?}", e);
            }
        }

        self.stop_flag.store(true, Ordering::SeqCst);

        if let Some(key_listener) = self.key_thread.take() {
            if let Err(e) = key_listener.join() {
                eprintln!("Error joining key listener thread: {:?}", e);
            }
        }
    }
}