clin-rs 0.8.20

Encrypted terminal note-taking app inspired by Obsidian
use std::sync::{Arc, RwLock, mpsc};

use super::graph::GraphState;
use crate::config::ClinConfig;

pub fn start_physics(
    state: Arc<RwLock<GraphState>>,
    _config: &ClinConfig,
    kill_rx: mpsc::Receiver<()>,
) {
    let gravity = 0.01;
    let timestep = 0.016;
    let sleep_ms = 16;

    std::thread::spawn(move || {
        loop {
            match kill_rx.try_recv() {
                Ok(_) | Err(mpsc::TryRecvError::Disconnected) => break,
                Err(mpsc::TryRecvError::Empty) => {}
            }

            let should_update = {
                let guard = state.read().unwrap_or_else(|e| e.into_inner());
                !guard.is_settled
            };

            if should_update {
                let new_bounds = {
                    let mut guard = state.write().unwrap_or_else(|e| e.into_inner());
                    guard.simulation.update(timestep as f32);

                    if let Some((tx, ty)) = guard.drag_target
                        && let Some(idx) = guard.dragging_node
                    {
                        let graph = guard.simulation.get_graph_mut();
                        if let Some(node) = graph.node_weight_mut(idx) {
                            node.location.x = tx;
                            node.location.y = ty;
                            node.velocity = fdg_sim::glam::Vec3::ZERO;
                        }
                    }

                    if gravity > 0.0 {
                        let graph = guard.simulation.get_graph_mut();
                        for node in graph.node_weights_mut() {
                            node.velocity.x -= node.location.x * gravity as f32;
                            node.velocity.y -= node.location.y * gravity as f32;
                        }
                    }

                    let graph = guard.simulation.get_graph();
                    let energy: f32 = graph.node_weights().map(|n| n.velocity.length()).sum();

                    if energy < 0.05 * graph.node_count() as f32 {
                        guard.is_settled = true;
                    }

                    super::render::compute_graph_bounds(guard.simulation.get_graph())
                };

                {
                    let mut guard = state.write().unwrap_or_else(|e| e.into_inner());
                    guard.graph_bounds = new_bounds;
                }
            } else {
                std::thread::sleep(std::time::Duration::from_millis(sleep_ms * 6));
                continue;
            }

            std::thread::sleep(std::time::Duration::from_millis(sleep_ms));
        }
    });
}
#[cfg(test)]
mod tests {
    use super::*;

    fn should_stop(res: Result<(), mpsc::TryRecvError>) -> bool {
        matches!(res, Ok(_) | Err(mpsc::TryRecvError::Disconnected))
    }

    #[test]
    fn test_physics_stop_condition() {
        assert!(should_stop(Ok(())));
        assert!(!should_stop(Err(mpsc::TryRecvError::Empty)));
        assert!(should_stop(Err(mpsc::TryRecvError::Disconnected)));
    }
}