//! Preparing a scenario for a run. use crate::activity::Activity; use crate::error::{SimulationError, SimulationErrorKind}; use crate::session::Session; use apsis_resource::ResourceSet; use apsis_scenario::Scenario; use apsis_schedule::{Evaluation, Schedule}; use apsis_scheduler::{Planner, SchedulingResult}; use apsis_task::{Task, TaskId}; /// A scenario prepared for a run. /// /// A simulation holds the scenario, the plan that was made from it, and the /// timeline that plan implies. Preparing is separate from running so that a /// caller can inspect the plan, and reject it, before a run commits to it. /// /// 为一次运行准备好的场景。 /// /// 仿真保存场景、由其作出的计划,以及该计划所蕴含的时间线。准备与运行 /// 分开,使调用方可以在运行承诺之前检查该计划并拒绝它。 #[derive(Debug, Clone)] pub struct Simulation { scenario: Scenario, resources: ResourceSet, result: SchedulingResult, timeline: Vec<(f64, Activity)>, } impl Simulation { /// Checks a scenario and plans it. /// /// The scenario is validated, the tasks are planned against the /// resources, and the resulting schedule is turned into a timeline of /// start and end events. The plan is greedy, so a scenario may be /// prepared successfully and still leave tasks unplaced; that is reported /// by [`Simulation::has_full_plan`] rather than being an error. /// /// 检查场景并为其作计划。 /// /// 场景被校验,任务针对资源被规划,所得调度被转为由开始与结束事件 /// 构成的时间线。该计划是贪心的,故场景可能被成功准备却仍留下未 /// 放置的任务;这由 [`Simulation::has_full_plan`] 报告,而不是当作 /// 错误。 pub fn prepare(scenario: Scenario) -> Result { scenario.validate().map_err(|error| { SimulationError::new(SimulationErrorKind::InvalidScenario) .with_detail(error.to_string()) })?; let resources = scenario.resource_set().map_err(|error| { SimulationError::new(SimulationErrorKind::InvalidResources) .with_detail(error.to_string()) })?; let tasks = scenario.tasks().to_vec(); let result = Planner::new().schedule_with_precedence(&tasks, &resources, scenario.precedence()); Self::from_parts(scenario, resources, result) } /// Builds a simulation from an already planned result. /// /// This is for a caller that has planned with its own settings and wants /// the rest of the facade: the timeline and the run loop. The result must /// belong to the scenario, which is checked. /// /// 由已规划好的结果构建仿真。 /// /// 这适用于已用自己的设置完成规划、只想要外观其余部分的调用方:时间 /// 线与运行循环。该结果必须属于该场景,这一点会被检查。 pub fn from_plan( scenario: Scenario, result: SchedulingResult, ) -> Result { scenario.validate().map_err(|error| { SimulationError::new(SimulationErrorKind::InvalidScenario) .with_detail(error.to_string()) })?; let resources = scenario.resource_set().map_err(|error| { SimulationError::new(SimulationErrorKind::InvalidResources) .with_detail(error.to_string()) })?; Self::from_parts(scenario, resources, result) } /// Builds the timeline from a plan. /// /// 由计划构建时间线。 fn from_parts( scenario: Scenario, resources: ResourceSet, result: SchedulingResult, ) -> Result { let mut timeline = Vec::with_capacity(result.schedule.len() * 2); for placement in result.schedule.in_time_order() { let Some(task) = scenario .tasks() .iter() .find(|task| task.id == placement.task) else { return Err(SimulationError::new(SimulationErrorKind::InconsistentPlan) .with_detail(format!("{} is not in the scenario", placement.task))); }; // The duration is copied into the placement, so the plan is // checked against the task it claims to place. // 时长被复制进放置中,故针对该计划声称要放置的任务检查它。 if (placement.duration - task.duration).abs() > f64::EPSILON { return Err(SimulationError::new(SimulationErrorKind::InconsistentPlan) .with_detail(format!( "{} lasts {} s in the plan and {} s as a task", placement.task, placement.duration, task.duration ))); } timeline.push((placement.start, Activity::start(placement.task))); timeline.push((placement.end(), Activity::end(placement.task))); } Ok(Self { scenario, resources, result, timeline, }) } /// Returns the scenario. /// /// 返回场景。 pub const fn scenario(&self) -> &Scenario { &self.scenario } /// Returns the resources in the scenario. /// /// 返回场景中的资源。 pub const fn resources(&self) -> &ResourceSet { &self.resources } /// Returns the planning result. /// /// 返回规划结果。 pub const fn plan(&self) -> &SchedulingResult { &self.result } /// Returns the schedule. /// /// 返回调度。 pub const fn schedule(&self) -> &Schedule { &self.result.schedule } /// Returns the tasks in the scenario. /// /// 返回场景中的任务。 pub fn tasks(&self) -> &[Task] { self.scenario.tasks() } /// Returns the timeline as time and activity pairs, in time order. /// /// Each placed task contributes a start and an end. Ties are ordered so /// that an end precedes a start at the same instant, which lets two back /// to back tasks share a boundary without the reader seeing them overlap. /// /// 按时间次序返回时间与活动对的列表。 /// /// 每处被放置的任务贡献一个开始与一个结束。并列情形按结束先于开始 /// 排序,使两个前后相接的任务可共享一个边界而读者不会看到它们 /// 重叠。 pub fn timeline(&self) -> &[(f64, Activity)] { &self.timeline } /// Returns the number of start and end events in the timeline. /// /// 返回时间线中开始与结束事件的数量。 pub fn activity_count(&self) -> usize { self.timeline.len() } /// Returns `true` when every offered task was placed. /// /// 当所有提交任务均被放置时返回 `true`。 pub fn has_full_plan(&self) -> bool { self.result.is_complete() } /// Returns `true` when every mandatory task was placed. /// /// 当每个必需任务都被放置时返回 `true`。 pub fn honours_mandatory(&self) -> bool { self.result.honours_mandatory(self.scenario.tasks()) } /// Returns `true` when every precedence constraint is satisfied. /// /// 当每条先后约束都被满足时返回 `true`。 pub fn honours_precedence(&self) -> bool { self.result.honours_precedence(self.scenario.precedence()) } /// Returns the tasks that were not placed for want of time. /// /// 返回因时间不足而未被放置的任务。 pub fn deferred(&self) -> Vec { self.result.deferred() } /// Returns the tasks that cannot be placed at all. /// /// 返回完全无法放置的任务。 pub fn infeasible(&self) -> Vec { self.result.infeasible() } /// Evaluates the schedule against the scenario. /// /// 针对场景评估该调度。 pub fn evaluate(&self) -> Evaluation { self.schedule() .evaluate(self.scenario.tasks(), &self.resources) } /// Starts a run. /// /// The returned session owns its own engine and recorder, so several /// sessions may run from one prepared simulation without interfering. The /// clock starts at the scenario epoch and the run limits come from the /// scenario. /// /// 开始一次运行。 /// /// 返回的会话拥有自己的引擎与记录器,故多个会话可从同一个已准备 /// 仿真运行而互不干扰。时钟从场景历元开始,运行限制来自场景。 pub fn start(&self) -> Session { Session::new(self) } }