titanss 0.0.3

Titanss is a celestial simulation crate for the MilkyWay SolarSystem workspace
Documentation
use std::env;
use std::fs;
use std::path::PathBuf;
use std::sync::OnceLock;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SaturnRuntimeMode {
    Binary,
    Simulated,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SaturnContext {
    pub mode: SaturnRuntimeMode,
    pub orbital_radius_km: f64,
    pub orbital_period_days: f64,
    pub orbital_angle_deg: f64,
    pub rotation_period_h: f64,
    pub rotation_angle_deg: f64,
    pub axial_tilt_deg: f64,
    pub surface_speed_m_s: f64,
}

static PLANET_BINARY_CACHE: OnceLock<Option<PathBuf>> = OnceLock::new();

pub fn ensure_saturns_binary_or_simulate() -> SaturnContext {
    if find_planet_executable().is_some() {
        return reference_saturn_context(SaturnRuntimeMode::Binary);
    }

    simulated_saturn_context()
}

pub fn reference_saturn_context(mode: SaturnRuntimeMode) -> SaturnContext {
    SaturnContext {
        mode,
        orbital_radius_km: 1_433_500_000.0,
        orbital_period_days: 10_759.22,
        orbital_angle_deg: 0.0,
        rotation_period_h: 10.656,
        rotation_angle_deg: 0.0,
        axial_tilt_deg: 26.73,
        surface_speed_m_s: 9_870.0,
    }
}

pub fn simulated_saturn_context() -> SaturnContext {
    reference_saturn_context(SaturnRuntimeMode::Simulated)
}

pub fn saturn_sibling_satellites() -> &'static [&'static str] {
    &["enceladuss", "titans"]
}

fn find_planet_executable() -> Option<PathBuf> {
    PLANET_BINARY_CACHE
        .get_or_init(find_planet_executable_uncached)
        .clone()
}

fn find_planet_executable_uncached() -> Option<PathBuf> {
    let names = binary_names();

    names
        .iter()
        .find_map(|binary_name| {
            find_in_path(binary_name)
                .or_else(|| cargo_bin_dir().map(|directory| directory.join(binary_name)))
                .filter(|candidate| candidate.is_file())
        })
        .or_else(|| find_globally(names))
}

fn binary_names() -> &'static [&'static str] {
    if cfg!(windows) {
        &["saturns.exe", "saturn.exe", "saturns", "saturn"]
    } else {
        &["saturns", "saturn"]
    }
}

fn cargo_bin_dir() -> Option<PathBuf> {
    env::var_os("HOME").map(|home_dir| PathBuf::from(home_dir).join(".cargo/bin"))
}

fn find_in_path(binary_name: &str) -> Option<PathBuf> {
    let path_var = env::var_os("PATH")?;

    env::split_paths(&path_var)
        .map(|directory| directory.join(binary_name))
        .find(|candidate| candidate.is_file())
}

fn find_globally(binary_names: &[&str]) -> Option<PathBuf> {
    let mut stack = global_search_roots();

    while let Some(directory) = stack.pop() {
        let entries = match fs::read_dir(&directory) {
            Ok(entries) => entries,
            Err(_) => continue,
        };

        for entry in entries.flatten() {
            let file_type = match entry.file_type() {
                Ok(file_type) => file_type,
                Err(_) => continue,
            };

            let path = entry.path();

            if file_type.is_file() {
                let is_match = path
                    .file_name()
                    .and_then(|name| name.to_str())
                    .map(|name| binary_names.contains(&name))
                    .unwrap_or(false);

                if is_match {
                    return Some(path);
                }
            } else if file_type.is_dir() && !file_type.is_symlink() {
                stack.push(path);
            }
        }
    }

    None
}

fn global_search_roots() -> Vec<PathBuf> {
    if cfg!(windows) {
        let mut roots = Vec::new();

        for drive in b'A'..=b'Z' {
            let candidate = PathBuf::from(format!("{}:\\", drive as char));
            if candidate.is_dir() {
                roots.push(candidate);
            }
        }

        roots
    } else {
        vec![PathBuf::from("/")]
    }
}