mist 1.19.0

minimal, improved speedrun timer
use libloading::{Library, Symbol};
use mist_core::{
    config::get_plugin_dir,
    timer::{
        state::{RunUpdate, StateChangeRequest},
        Run,
    },
};
use mist_pdk::{InitFunc, ShutdownFunc, UpdateFunc, VersionFunc};
use std::{
    path::PathBuf,
    sync::mpsc::{Receiver, Sender},
};

pub enum PluginMsg {
    Update(RunUpdate),
    Shutdown,
}

pub struct PluginErr {
    pub ty: PluginErrTy,
    pub plugin: String,
}

impl PluginErr {
    fn missing_sym(plugin: String, sym: String) -> Self {
        Self {
            ty: PluginErrTy::MissingSym(sym),
            plugin,
        }
    }
    fn wrong_version(plugin: String, ver: u16) -> Self {
        Self {
            ty: PluginErrTy::WrongVersion(ver),
            plugin,
        }
    }
    fn filesystem(plugin: String) -> Self {
        Self {
            ty: PluginErrTy::Filesystem,
            plugin,
        }
    }
}

pub enum PluginErrTy {
    Filesystem,
    WrongVersion(u16),
    MissingSym(String),
}

struct PluginLib {
    plugin: String,
    lib: Library,
}

pub fn plugin_task(
    run: Run,
    rx: Receiver<PluginMsg>,
    tx: Sender<StateChangeRequest>,
    etx: Sender<PluginErr>,
) {
    let plugin_path = get_plugin_dir().unwrap_or(PathBuf::from("./plugins/"));
    if !plugin_path.exists() {
        return;
    }
    let mut libs = vec![];
    let mut updates = vec![];
    for file in std::fs::read_dir(plugin_path)
        .expect("Could not read plugin directory!")
        .map_while(|r| r.ok())
    {
        if file.file_type().is_ok_and(|t| t.is_file()) {
            let file_path = file.path();
            let plug_name = file
                .path()
                .file_stem()
                .map(|f| f.to_string_lossy())
                .unwrap_or_else(|| file_path.to_string_lossy().clone())
                .to_string();
            if let Ok(l) = unsafe { Library::new(file_path.clone()) } {
                libs.push(PluginLib {
                    lib: l,
                    plugin: plug_name,
                });
            } else {
                etx.send(PluginErr::filesystem(plug_name)).unwrap();
            }
        }
    }
    for i in 0..libs.len() {
        unsafe {
            let version = if let Ok(v) = libs[i]
                .lib
                .get::<Symbol<VersionFunc>>(b"mist_plugin_version")
                .or_else(|_| libs[i].lib.get::<Symbol<VersionFunc>>(b"version"))
            {
                v()
            } else {
                etx.send(PluginErr {
                    ty: PluginErrTy::MissingSym("mist_plugin_version".into()),
                    plugin: libs[i].plugin.clone(),
                })
                .unwrap();
                continue;
            };
            if version == mist_pdk::pdk_version() {
                if let Ok(i) = libs[i].lib.get::<Symbol<InitFunc>>(b"mist_plugin_init") {
                    i(&run);
                } else {
                    etx.send(PluginErr::missing_sym(
                        libs[i].plugin.clone(),
                        "mist_plugin_init".into(),
                    ))
                    .unwrap();
                    continue;
                }
                if let Ok(u) = libs[i].lib.get::<Symbol<UpdateFunc>>(b"mist_plugin_update") {
                    updates.push(u);
                } else {
                    etx.send(PluginErr::missing_sym(
                        libs[i].plugin.clone(),
                        "mist_plugin_update".into(),
                    ))
                    .unwrap();
                    continue;
                }
            } else {
                etx.send(PluginErr::wrong_version(libs[i].plugin.clone(), version))
                    .unwrap();
            }
        }
    }

    while let Ok(PluginMsg::Update(upd)) = rx.recv() {
        for f in &updates {
            let rq = f(&upd);
            if rq != StateChangeRequest::None {
                let _ = tx.send(rq);
            }
        }
    }

    for l in libs {
        unsafe {
            if let Ok(s) = l.lib.get::<Symbol<ShutdownFunc>>(b"mist_plugin_shutdown") {
                s();
            }
        }
    }
}