mudrs-milk 0.0.1

WIP Mud Client
Documentation
use std::{
    env,
    io::stdout,
    sync::Arc,
    time::Duration,
};

use anyhow::anyhow;
use crossterm::{
    event::{
        DisableMouseCapture,
        EnableMouseCapture,
    },
    execute,
    terminal::{
        EnterAlternateScreen,
        LeaveAlternateScreen,
    },
};
use futures::{
    channel::mpsc,
    lock::Mutex,
    prelude::*,
};
use tracing_subscriber::fmt::format::FmtSpan;
use mudrs_milk::{
    renderer::Renderer,
    script::{
        Flags,
        NopTelnetEngine,
        ScriptEngine,
    },
    user::User,
    widget::{
        input::*,
        scrollback::*,
    },
};
use tokio::time::timeout;
#[allow(unused_imports)]
use tracing::{
    debug,
    warn,
    error,
};
use tui::backend::{
    Backend,
    CrosstermBackend,
};

fn main() -> anyhow::Result<()> {
    let file_appender = tracing_appender::rolling::never(".", "milk.log");
    let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
    let filter = tracing_subscriber::EnvFilter::from_default_env();

    tracing_subscriber::fmt()
        .with_writer(non_blocking)
        .with_span_events(FmtSpan::NONE)
        .with_env_filter(filter)
        .init();

    std::panic::set_hook(Box::new(move |info| {
        let trace = backtrace::Backtrace::new();
        tracing::error!(?trace, ?info, "something panicked");
    }));

    let addr = env::args()
        .nth(1)
        .ok_or_else(|| anyhow!("Need server address"))?;

    // Terminal initialization
    crossterm::terminal::enable_raw_mode()?;
    execute!(stdout(), EnterAlternateScreen, EnableMouseCapture)?;
    let backend = CrosstermBackend::new(stdout());

    let rt = tokio::runtime::Runtime::new()?;

    let res = rt.block_on(async_main(backend, addr));

    execute!(stdout(), DisableMouseCapture, LeaveAlternateScreen)?;
    crossterm::terminal::disable_raw_mode()?;

    res
}

async fn async_main(backend: impl Backend, addr: String) -> anyhow::Result<()> {
    let flags = Flags::default();

    if !cfg!(windows) {
        use tokio::signal::unix;
        let mut term = unix::signal(unix::SignalKind::terminate())?;

        tokio::spawn({
            let flags = flags.clone();
            async move {
                while let Some(_) = term.recv().await {
                    flags.exit();
                }
            }
        });
    }

    let (input_tx, input_rx) = mpsc::channel(32);
    let (buffer_tx, buffer_rx) = mpsc::channel(32);
    let (render_tx, mut render_rx) = mpsc::channel(32);

    let scrollback = Arc::new(Mutex::new(ScrollbackBuffer::new(render_tx.clone())));
    let input = Arc::new(Mutex::new(InputBuffer::new(
        flags.clone(),
        input_tx.clone(),
        render_tx.clone(),
    )));
    let mut renderer = Renderer::new(backend, input.clone(), scrollback.clone(), 60f64)?;

    let user_input = User::new();

    user_input.dispatch_events(input.clone(), scrollback.clone(), render_tx.clone());

    ScrollbackBuffer::append_lines(scrollback, buffer_rx);

    NopTelnetEngine(addr).start(input_rx, buffer_tx, flags.clone())?;

    loop {
        let res = timeout(Duration::from_secs(5), render_rx.next()).await;
        if res.is_err() {
            debug!("timed out waiting for render tick");
        }

        if flags.should_exit() {
            break;
        }

        renderer.render().await?;
    }

    Ok(())
}