radiate-ui 1.3.1

A Rust library for genetic algorithms and artificial evolution.
Documentation
use crate::app::{App, GenerationEvent, InputEvent};
use color_eyre::{Result, eyre::Context};
use crossbeam::channel;
use radiate_engines::{
    Chromosome, Engine, EngineState, EngineStream, Generation, GenerationView, GeneticEngine,
    error::RadiateResult, events::LogEvent, sync::IntoPair,
};
use radiate_engines::{EngineRuntime, EvolutionContext, ThreadSync};
use std::{
    sync::{Arc, atomic::Ordering},
    time::Duration,
};

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

pub struct TuiEngine<C, T>
where
    C: Chromosome,
    T: Clone + Send + Sync + 'static,
{
    inner: GeneticEngine<C, T>,
    control: ThreadSync,
    dispatcher: channel::Sender<InputEvent<C>>,
    app_thread: Option<std::thread::JoinHandle<Result<()>>>,
    key_thread: Option<std::thread::JoinHandle<Result<()>>>,
}

impl<C, T> TuiEngine<C, T>
where
    C: Chromosome + Clone + 'static,
    T: Clone + Send + Sync + 'static,
{
    pub fn new(mut inner: GeneticEngine<C, T>, render_interval: Duration) -> Self {
        let control = inner.control();
        let app = App::new(render_interval, control.clone());

        let (dispatch_one, dispatch_two) = app.dispatcher().into_pair();
        let stop_flag = control.stop_flag();

        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_flag.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,
            control,
            dispatcher: dispatch_one,
            app_thread: Some(app_thread),
            key_thread: Some(key_thread),
        }
    }

    pub fn iter(self) -> EngineRuntime<Self> {
        let dispatcher = self.dispatcher.clone();
        EngineRuntime::new(self).subscribe::<LogEvent>(move |event: &LogEvent| {
            dispatcher
                .send(InputEvent::Log(event.0, event.1.clone()))
                .map_err(|_| eprintln!("Failed to send log event: {:?}", event))
                .unwrap();
        })
    }
}

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

    fn context(&self) -> &Self::Ctx {
        self.inner.context()
    }

    fn epoch(&self) -> Self::Epoch {
        self.inner.epoch()
    }

    fn state(&self) -> EngineState {
        self.inner.state()
    }

    fn start(&mut self) {
        self.inner.start();
    }

    fn stop(&mut self) {
        self.inner.stop();
    }

    #[inline]
    fn step(&mut self) -> RadiateResult<()> {
        self.inner.step()?;
        let state = self.inner.state();
        let current = self.inner.context();

        if matches!(state, EngineState::Stopped) {
            return Ok(());
        }

        if current.index() == 1 {
            self.dispatcher
                .send(InputEvent::EngineStart(Arc::clone(&current.front())))
                .unwrap();
        }

        let event = GenerationEvent::from(current);
        self.dispatcher
            .send(InputEvent::EpochComplete(event))
            .unwrap();

        Ok(())
    }
}

impl<C, T> EngineStream for TuiEngine<C, T>
where
    C: Chromosome + Clone + 'static,
    T: Clone + Send + Sync + 'static,
{
    type View<'a>
        = GenerationView<'a, C, T>
    where
        Self: 'a;

    fn run<F>(mut self, limit: F) -> RadiateResult<Self::Epoch>
    where
        F: Fn(Self::View<'_>) -> bool + 'static,
    {
        loop {
            self.step()?;
            if matches!(self.state(), EngineState::Stopped) {
                break Ok(self.epoch());
            }

            let current = self.inner.context();

            if limit(GenerationView::new(current)) {
                break Ok(self.epoch());
            }
        }
    }
}

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

        self.control.set_paused(false);

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

        self.control.stop();

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