solarsystems 0.0.1

N-body solar system engine — gravitational dynamics, orbital mechanics, perturbations, event detection, and full celestial orchestration
Documentation
use crate::config::parameters::default_bodies;
use crate::dynamics::integrator::Integrator;
use crate::orchestrator::pipeline::{Pipeline, RunBudget};

pub struct Scheduler {
    pub pipeline: Pipeline,
    pub output_interval_steps: usize,
    pub current_step: usize,
}

impl Scheduler {
    pub fn default_system(dt: f64) -> Self {
        let bodies = default_bodies();
        let pipeline = Pipeline::new(bodies, dt, Integrator::LeapfrogKDK);
        Self {
            pipeline,
            output_interval_steps: 100,
            current_step: 0,
        }
    }

    pub fn with_integrator(dt: f64, integrator: Integrator) -> Self {
        let bodies = default_bodies();
        let pipeline = Pipeline::new(bodies, dt, integrator);
        Self {
            pipeline,
            output_interval_steps: 100,
            current_step: 0,
        }
    }

    pub fn run(&mut self, total_steps: usize) {
        let executed = self
            .pipeline
            .run_with_budget(total_steps, RunBudget::default());
        self.current_step += executed;
    }

    pub fn run_budgeted(&mut self, total_steps: usize, budget: RunBudget) {
        let executed = self.pipeline.run_with_budget(total_steps, budget);
        self.current_step += executed;
    }

    pub fn run_with_callback<F>(&mut self, total_steps: usize, callback: F)
    where
        F: FnMut(usize, &Pipeline),
    {
        self.run_with_callback_budgeted(total_steps, RunBudget::default(), callback);
    }

    pub fn run_with_callback_budgeted<F>(
        &mut self,
        total_steps: usize,
        budget: RunBudget,
        mut callback: F,
    ) where
        F: FnMut(usize, &Pipeline),
    {
        self.pipeline.initialize();
        let capped_steps = budget.max_steps.map_or(total_steps, |m| m.min(total_steps));
        let deadline = budget
            .max_runtime_ms
            .map(|ms| std::time::Instant::now() + std::time::Duration::from_millis(ms));

        for idx in 0..capped_steps {
            if let Some(t) = deadline
                && std::time::Instant::now() >= t
            {
                break;
            }

            self.pipeline.step();
            self.current_step += 1;
            if self.current_step.is_multiple_of(self.output_interval_steps) {
                callback(self.current_step, &self.pipeline);
            }

            if budget.yield_every > 0 && (idx + 1).is_multiple_of(budget.yield_every) {
                std::thread::yield_now();
            }
        }
    }

    pub fn elapsed_years(&self) -> f64 {
        self.pipeline.time.years_elapsed()
    }

    pub fn elapsed_days(&self) -> f64 {
        self.pipeline.time.days_elapsed()
    }

    pub fn event_count(&self) -> usize {
        self.pipeline.events.len()
    }
}