diff --git a/crates/ops/apsis-schedule/src/error.rs b/crates/ops/apsis-schedule/src/error.rs index c5090e3..43f9dab 100644 --- a/crates/ops/apsis-schedule/src/error.rs +++ b/crates/ops/apsis-schedule/src/error.rs @@ -3,8 +3,8 @@ use core::fmt; /// The category of a schedule error. - /// - /// 计划错误的类别。 +/// +/// 计划错误的类别。 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum ScheduleErrorKind { @@ -54,8 +54,8 @@ impl fmt::Display for ScheduleErrorKind { } /// A failure while building or evaluating a schedule. - /// - /// 构建或评估计划时发生的失败。 +/// +/// 构建或评估计划时发生的失败。 #[derive(Debug, Clone, PartialEq, Eq)] pub struct ScheduleError { kind: ScheduleErrorKind, diff --git a/crates/ops/apsis-schedule/src/placement.rs b/crates/ops/apsis-schedule/src/placement.rs index 91cb981..a9609bf 100644 --- a/crates/ops/apsis-schedule/src/placement.rs +++ b/crates/ops/apsis-schedule/src/placement.rs @@ -4,21 +4,33 @@ use apsis_resource::ResourceId; use apsis_task::TaskId; /// One task placed on the schedule. +/// +/// 计划上放置的一个任务。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct Placement { /// The task placed. + /// + /// 被放置的任务。 pub task: TaskId, /// The start time of the placement, in seconds. + /// + /// 放置的起始时刻,单位为秒。 pub start: f64, /// The duration of the placement, in seconds. /// /// This is copied from the task so that a schedule carries everything /// needed to evaluate it without the tasks to hand. + /// + /// 放置的时长,单位为秒。 + /// + /// 它从任务复制而来,故一份计划无需持有任务集合也能完成评估。 pub duration: f64, } impl Placement { /// Creates a placement. + /// + /// 创建一次放置。 pub const fn new(task: TaskId, start: f64, duration: f64) -> Self { Self { task, @@ -28,6 +40,8 @@ impl Placement { } /// Returns the end time of the placement, in seconds. + /// + /// 返回放置的结束时刻,单位为秒。 pub fn end(&self) -> f64 { self.start + self.duration } @@ -37,31 +51,50 @@ impl Placement { /// The interval is half-open, so a placement covers its start and not its /// end. That convention lets two back to back placements share a boundary /// without overlapping. + /// + /// 当该放置覆盖某瞬时返回 `true`。 + /// + /// 区间是半开的,故放置含其起点而不含其终点。该约定使两个首尾相接的 + /// 放置可共享边界而不重叠。 pub fn covers(&self, time: f64) -> bool { time >= self.start && time < self.end() } /// Returns `true` when the placement overlaps another. + /// + /// 当该放置与另一个放置重叠时返回 `true`。 pub fn overlaps(&self, other: &Self) -> bool { self.start < other.end() && other.start < self.end() } /// Returns `true` when the placement is usable. + /// + /// 当该放置可用时返回 `true`。 pub fn is_valid(&self) -> bool { self.start.is_finite() && self.duration.is_finite() && self.duration >= 0.0 } } /// How much of one resource a schedule uses. +/// +/// 一份计划对某个资源的使用情况。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct ResourceUsage { /// The resource. + /// + /// 该资源。 pub resource: ResourceId, /// The greatest total demand placed on the resource at any instant. + /// + /// 该资源上任意瞬时的最大总需求。 pub peak_demand: f64, /// The time during which the resource is used at all, in seconds. + /// + /// 该资源被使用的总时长,单位为秒。 pub busy_seconds: f64, /// The number of placements that use the resource. + /// + /// 使用该资源的放置数量。 pub placement_count: usize, } @@ -73,6 +106,13 @@ impl ResourceUsage { /// only half occupied by demand still counts as busy. /// /// Returns `None` when the interval is not positive. + /// + /// 返回该资源在某区间上的利用率。 + /// + /// 其值为忙碌时长除以区间长度。这是时间利用率而非容量利用率:仅被 + /// 需求占据一半的资源同样算作忙碌。 + /// + /// 区间非正时返回 `None`。 pub fn utilisation(&self, interval_seconds: f64) -> Option { if interval_seconds > 0.0 && interval_seconds.is_finite() { Some(self.busy_seconds / interval_seconds) @@ -83,19 +123,33 @@ impl ResourceUsage { } /// The evaluation of a whole schedule. +/// +/// 整份计划的评估结果。 #[derive(Debug, Clone, PartialEq)] pub struct Evaluation { /// The number of placements. + /// + /// 放置的数量。 pub placed_count: usize, /// The number of tasks that were offered to the schedule. + /// + /// 提交给该计划的任务数量。 pub offered_count: usize, /// The sum of the placed durations, in seconds. + /// + /// 已放置时长之和,单位为秒。 pub scheduled_seconds: f64, /// The earliest start time, or zero when nothing is placed. + /// + /// 最早的开始时刻;无放置时为零。 pub makespan_start: f64, /// The latest end time, or zero when nothing is placed. + /// + /// 最晚的结束时刻;无放置时为零。 pub makespan_end: f64, /// The usage of each resource, in identifier order. + /// + /// 各资源的使用情况,按标识符顺序排列。 pub resource_usage: Vec, } @@ -105,6 +159,10 @@ impl Evaluation { /// /// This is the time from the earliest start to the latest end, which is /// the makespan of the plan. + /// + /// 返回该计划覆盖的规划时域长度,单位为秒。 + /// + /// 它是从最早开始到最晚结束的时间,即该计划的 makespan。 pub fn makespan(&self) -> f64 { (self.makespan_end - self.makespan_start).max(0.0) } @@ -112,6 +170,10 @@ impl Evaluation { /// Returns the fraction of the offered tasks that were placed. /// /// Returns `None` when no tasks were offered. + /// + /// 返回所提交任务中被放置的比例。 + /// + /// 没有提交任务时返回 `None`。 pub fn fill_rate(&self) -> Option { if self.offered_count == 0 { return None; @@ -120,11 +182,15 @@ impl Evaluation { } /// Returns `true` when every offered task was placed. + /// + /// 当所有提交任务均被放置时返回 `true`。 pub fn is_complete(&self) -> bool { self.placed_count == self.offered_count } /// Returns the usage of one resource. + /// + /// 返回某个资源的使用情况。 pub fn usage_of(&self, resource: ResourceId) -> Option<&ResourceUsage> { self.resource_usage .iter() diff --git a/crates/ops/apsis-schedule/src/schedule.rs b/crates/ops/apsis-schedule/src/schedule.rs index 3c3d709..c75e5ea 100644 --- a/crates/ops/apsis-schedule/src/schedule.rs +++ b/crates/ops/apsis-schedule/src/schedule.rs @@ -13,6 +13,12 @@ use std::collections::BTreeMap; /// may be placed at most once: placing it again is an error rather than a /// silent overwrite, because a schedule that contains two placements for one /// task is not a schedule. +/// +/// 一组任务放置。 +/// +/// 该计划按放置发生的顺序保存各项,并维护从任务到放置的索引,故按任务查找 +/// 是直接的。一个任务至多放置一次:重复放置是错误而非静默覆盖,因为含有 +/// 同一任务两处放置的集合已不是一份计划。 #[derive(Debug, Clone, PartialEq, Default)] pub struct Schedule { placements: Vec, @@ -21,6 +27,8 @@ pub struct Schedule { impl Schedule { /// Creates an empty schedule. + /// + /// 创建一份空计划。 pub fn new() -> Self { Self { placements: Vec::new(), @@ -29,26 +37,36 @@ impl Schedule { } /// Returns the placements in the order they were made. + /// + /// 返回按放置顺序排列的各项。 pub fn placements(&self) -> &[Placement] { &self.placements } /// Returns the number of placements. + /// + /// 返回放置的数量。 pub fn len(&self) -> usize { self.placements.len() } /// Returns `true` when there are no placements. + /// + /// 当没有放置时返回 `true`。 pub fn is_empty(&self) -> bool { self.placements.is_empty() } /// Returns `true` when a task has been placed. + /// + /// 当某任务已被放置时返回 `true`。 pub fn is_placed(&self, task: TaskId) -> bool { self.index.contains_key(&task.index()) } /// Returns the placement of a task. + /// + /// 返回某任务的放置。 pub fn placement_of(&self, task: TaskId) -> Option<&Placement> { self.index .get(&task.index()) @@ -64,6 +82,14 @@ impl Schedule { /// /// Returns an error when the task is already placed, when the start time /// is not finite, or when the placement falls outside the task's window. + /// + /// 把某任务放置在某时刻。 + /// + /// 放置会对照该任务的窗口校验。任务的资源通过任务本身随放置一同记录, + /// 故本方法无需它们;容量检查由需要资源的 + /// [`Schedule::validate_capacity`] 完成。 + /// + /// 该任务已被放置、起始时刻非有限,或放置落在任务窗口之外时返回错误。 pub fn place( &mut self, task: &apsis_task::Task, @@ -107,6 +133,10 @@ impl Schedule { /// Removes the placement of a task. /// /// Returns an error when the task is not placed. + /// + /// 移除某任务的放置。 + /// + /// 该任务未被放置时返回错误。 pub fn unplace(&mut self, task: TaskId) -> Result { let position = self.index.remove(&task.index()).ok_or_else(|| { ScheduleError::new(ScheduleErrorKind::NotPlaced).with_detail(format!("{task}")) @@ -115,11 +145,15 @@ impl Schedule { // Every placement after the removed one shifts down by one, so the // index must be rebuilt. The schedule is small enough that this is // cheaper than maintaining a linked structure. + // 被移除项之后的每项都前移一位,故索引须重建。计划规模足够小, + // 这比维护链式结构更划算。 self.rebuild_index(); Ok(removed) } /// Rebuilds the task to position index. + /// + /// 重建从任务到位置的索引。 fn rebuild_index(&mut self) { self.index.clear(); for (position, placement) in self.placements.iter().enumerate() { @@ -128,6 +162,8 @@ impl Schedule { } /// Returns the placements that use a resource, in time order. + /// + /// 返回使用某资源的放置,按时间排序。 pub fn placements_on( &self, resource: ResourceId, @@ -159,6 +195,12 @@ impl Schedule { /// tasks on disjoint resources may legitimately overlap, so a caller that /// wants to know whether a resource is overbooked should use /// [`Schedule::validate_capacity`] instead. + /// + /// 当两个不同任务的放置重叠时返回 `true`。 + /// + /// 这是计划与自身的比较,忽略资源。位于不相交资源上的两个任务本就可 + /// 以合法重叠,故欲知某资源是否超订的调用方应改用 + /// [`Schedule::validate_capacity`]。 pub fn has_overlapping_placements(&self) -> bool { for (index, placement) in self.placements.iter().enumerate() { if self.placements[..index] @@ -172,6 +214,8 @@ impl Schedule { } /// Returns the total placed duration, in seconds. + /// + /// 返回已放置的总时长,单位为秒。 pub fn scheduled_seconds(&self) -> f64 { self.placements .iter() @@ -182,6 +226,10 @@ impl Schedule { /// Returns the earliest start of any placement. /// /// Returns `None` when the schedule is empty. + /// + /// 返回所有放置中最早的开始时刻。 + /// + /// 计划为空时返回 `None`。 pub fn earliest_start(&self) -> Option { self.placements .iter() @@ -196,6 +244,10 @@ impl Schedule { /// Returns the latest end of any placement. /// /// Returns `None` when the schedule is empty. + /// + /// 返回所有放置中最晚的结束时刻。 + /// + /// 计划为空时返回 `None`。 pub fn latest_end(&self) -> Option { self.placements .iter() @@ -219,6 +271,17 @@ impl Schedule { /// between boundaries. /// /// Returns an error describing the first violation. + /// + /// 对照一组资源校验该计划。 + /// + /// 检查分两部分。其一,每处放置须与其任务的需求一致,这可捕获用另 + /// 一份任务列表建成的计划。其二,对每个资源,任意瞬时的总需求不得超 + /// 过容量。 + /// + /// 第二部分按时间序遍历各资源的放置边界,由于相邻边界之间总需求恒定, + /// 该遍历是精确的。 + /// + /// 返回描述首个违规的错误。 pub fn validate_capacity( &self, tasks: &[apsis_task::Task], @@ -243,6 +306,7 @@ impl Schedule { for resource in resources.resources() { // The boundaries at which the total demand can change. + // 总需求可能发生变化的各边界。 let mut events: Vec<(f64, f64)> = Vec::new(); for placement in &self.placements { let Some(task) = tasks.iter().find(|task| task.id == placement.task) else { @@ -259,6 +323,8 @@ impl Schedule { // Sort by time, with a release before an acquisition at the same // instant so that two placements that touch are not counted as // overlapping. + // 按时间排序;同一瞬先释放后获取,使两个相接的放置不被计为 + // 重叠。 events.sort_by(|first, second| { first .0 @@ -294,6 +360,11 @@ impl Schedule { /// The evaluation reports the counts, the makespan, and the usage of each /// resource. The peak demand of a resource is found by the same event /// sweep that the capacity check uses, so the two agree. + /// + /// 对照任务列表与资源集合评估该计划。 + /// + /// 评估给出各项计数、makespan 以及每个资源的使用情况。资源的峰值 + /// 需求由与容量检查相同的事件扫掠求得,故二者一致。 pub fn evaluate( &self, tasks: &[apsis_task::Task], @@ -364,6 +435,11 @@ impl Schedule { /// A schedule is stored in the order it was built, which is the order a /// scheduler considered the tasks. A caller that wants to read the plan /// in time order uses this. + /// + /// 返回按开始时刻排序的计划副本,原计划不变。 + /// + /// 计划按其构建顺序存储,即调度器考虑任务的顺序。欲按时间阅读该计 + /// 划的调用方使用本方法。 pub fn in_time_order(&self) -> Vec { let mut ordered = self.placements.clone(); ordered.sort_by(|first, second| { @@ -381,6 +457,11 @@ impl Schedule { /// /// Returns `None` when the resource is idle there. This is what a /// sequential scheduler asks when it looks for the next gap. + /// + /// 若某资源在给定瞬时忙碌,返回其变为空闲的时刻。 + /// + /// 该资源在彼处空闲时返回 `None`。顺序调度器寻找下一个空隙时正是 + /// 询问这一点。 pub fn resource_busy_until( &self, resource: ResourceId,