docs: bilingual comments for apsis-schedule

This commit is contained in:
ApsisRS contributors 2026-10-02 01:03:18 +08:00
parent 0a221e23b7
commit 175abb3f68
3 changed files with 151 additions and 4 deletions

View File

@ -3,8 +3,8 @@
use core::fmt; use core::fmt;
/// The category of a schedule error. /// The category of a schedule error.
/// ///
/// 计划错误的类别。 /// 计划错误的类别。
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive] #[non_exhaustive]
pub enum ScheduleErrorKind { pub enum ScheduleErrorKind {
@ -54,8 +54,8 @@ impl fmt::Display for ScheduleErrorKind {
} }
/// A failure while building or evaluating a schedule. /// A failure while building or evaluating a schedule.
/// ///
/// 构建或评估计划时发生的失败。 /// 构建或评估计划时发生的失败。
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct ScheduleError { pub struct ScheduleError {
kind: ScheduleErrorKind, kind: ScheduleErrorKind,

View File

@ -4,21 +4,33 @@ use apsis_resource::ResourceId;
use apsis_task::TaskId; use apsis_task::TaskId;
/// One task placed on the schedule. /// One task placed on the schedule.
///
/// 计划上放置的一个任务。
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct Placement { pub struct Placement {
/// The task placed. /// The task placed.
///
/// 被放置的任务。
pub task: TaskId, pub task: TaskId,
/// The start time of the placement, in seconds. /// The start time of the placement, in seconds.
///
/// 放置的起始时刻,单位为秒。
pub start: f64, pub start: f64,
/// The duration of the placement, in seconds. /// The duration of the placement, in seconds.
/// ///
/// This is copied from the task so that a schedule carries everything /// This is copied from the task so that a schedule carries everything
/// needed to evaluate it without the tasks to hand. /// needed to evaluate it without the tasks to hand.
///
/// 放置的时长,单位为秒。
///
/// 它从任务复制而来,故一份计划无需持有任务集合也能完成评估。
pub duration: f64, pub duration: f64,
} }
impl Placement { impl Placement {
/// Creates a placement. /// Creates a placement.
///
/// 创建一次放置。
pub const fn new(task: TaskId, start: f64, duration: f64) -> Self { pub const fn new(task: TaskId, start: f64, duration: f64) -> Self {
Self { Self {
task, task,
@ -28,6 +40,8 @@ impl Placement {
} }
/// Returns the end time of the placement, in seconds. /// Returns the end time of the placement, in seconds.
///
/// 返回放置的结束时刻,单位为秒。
pub fn end(&self) -> f64 { pub fn end(&self) -> f64 {
self.start + self.duration self.start + self.duration
} }
@ -37,31 +51,50 @@ impl Placement {
/// The interval is half-open, so a placement covers its start and not its /// 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 /// end. That convention lets two back to back placements share a boundary
/// without overlapping. /// without overlapping.
///
/// 当该放置覆盖某瞬时返回 `true`。
///
/// 区间是半开的,故放置含其起点而不含其终点。该约定使两个首尾相接的
/// 放置可共享边界而不重叠。
pub fn covers(&self, time: f64) -> bool { pub fn covers(&self, time: f64) -> bool {
time >= self.start && time < self.end() time >= self.start && time < self.end()
} }
/// Returns `true` when the placement overlaps another. /// Returns `true` when the placement overlaps another.
///
/// 当该放置与另一个放置重叠时返回 `true`。
pub fn overlaps(&self, other: &Self) -> bool { pub fn overlaps(&self, other: &Self) -> bool {
self.start < other.end() && other.start < self.end() self.start < other.end() && other.start < self.end()
} }
/// Returns `true` when the placement is usable. /// Returns `true` when the placement is usable.
///
/// 当该放置可用时返回 `true`。
pub fn is_valid(&self) -> bool { pub fn is_valid(&self) -> bool {
self.start.is_finite() && self.duration.is_finite() && self.duration >= 0.0 self.start.is_finite() && self.duration.is_finite() && self.duration >= 0.0
} }
} }
/// How much of one resource a schedule uses. /// How much of one resource a schedule uses.
///
/// 一份计划对某个资源的使用情况。
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct ResourceUsage { pub struct ResourceUsage {
/// The resource. /// The resource.
///
/// 该资源。
pub resource: ResourceId, pub resource: ResourceId,
/// The greatest total demand placed on the resource at any instant. /// The greatest total demand placed on the resource at any instant.
///
/// 该资源上任意瞬时的最大总需求。
pub peak_demand: f64, pub peak_demand: f64,
/// The time during which the resource is used at all, in seconds. /// The time during which the resource is used at all, in seconds.
///
/// 该资源被使用的总时长,单位为秒。
pub busy_seconds: f64, pub busy_seconds: f64,
/// The number of placements that use the resource. /// The number of placements that use the resource.
///
/// 使用该资源的放置数量。
pub placement_count: usize, pub placement_count: usize,
} }
@ -73,6 +106,13 @@ impl ResourceUsage {
/// only half occupied by demand still counts as busy. /// only half occupied by demand still counts as busy.
/// ///
/// Returns `None` when the interval is not positive. /// Returns `None` when the interval is not positive.
///
/// 返回该资源在某区间上的利用率。
///
/// 其值为忙碌时长除以区间长度。这是时间利用率而非容量利用率:仅被
/// 需求占据一半的资源同样算作忙碌。
///
/// 区间非正时返回 `None`。
pub fn utilisation(&self, interval_seconds: f64) -> Option<f64> { pub fn utilisation(&self, interval_seconds: f64) -> Option<f64> {
if interval_seconds > 0.0 && interval_seconds.is_finite() { if interval_seconds > 0.0 && interval_seconds.is_finite() {
Some(self.busy_seconds / interval_seconds) Some(self.busy_seconds / interval_seconds)
@ -83,19 +123,33 @@ impl ResourceUsage {
} }
/// The evaluation of a whole schedule. /// The evaluation of a whole schedule.
///
/// 整份计划的评估结果。
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Evaluation { pub struct Evaluation {
/// The number of placements. /// The number of placements.
///
/// 放置的数量。
pub placed_count: usize, pub placed_count: usize,
/// The number of tasks that were offered to the schedule. /// The number of tasks that were offered to the schedule.
///
/// 提交给该计划的任务数量。
pub offered_count: usize, pub offered_count: usize,
/// The sum of the placed durations, in seconds. /// The sum of the placed durations, in seconds.
///
/// 已放置时长之和,单位为秒。
pub scheduled_seconds: f64, pub scheduled_seconds: f64,
/// The earliest start time, or zero when nothing is placed. /// The earliest start time, or zero when nothing is placed.
///
/// 最早的开始时刻;无放置时为零。
pub makespan_start: f64, pub makespan_start: f64,
/// The latest end time, or zero when nothing is placed. /// The latest end time, or zero when nothing is placed.
///
/// 最晚的结束时刻;无放置时为零。
pub makespan_end: f64, pub makespan_end: f64,
/// The usage of each resource, in identifier order. /// The usage of each resource, in identifier order.
///
/// 各资源的使用情况,按标识符顺序排列。
pub resource_usage: Vec<ResourceUsage>, pub resource_usage: Vec<ResourceUsage>,
} }
@ -105,6 +159,10 @@ impl Evaluation {
/// ///
/// This is the time from the earliest start to the latest end, which is /// This is the time from the earliest start to the latest end, which is
/// the makespan of the plan. /// the makespan of the plan.
///
/// 返回该计划覆盖的规划时域长度,单位为秒。
///
/// 它是从最早开始到最晚结束的时间,即该计划的 makespan。
pub fn makespan(&self) -> f64 { pub fn makespan(&self) -> f64 {
(self.makespan_end - self.makespan_start).max(0.0) (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 the fraction of the offered tasks that were placed.
/// ///
/// Returns `None` when no tasks were offered. /// Returns `None` when no tasks were offered.
///
/// 返回所提交任务中被放置的比例。
///
/// 没有提交任务时返回 `None`。
pub fn fill_rate(&self) -> Option<f64> { pub fn fill_rate(&self) -> Option<f64> {
if self.offered_count == 0 { if self.offered_count == 0 {
return None; return None;
@ -120,11 +182,15 @@ impl Evaluation {
} }
/// Returns `true` when every offered task was placed. /// Returns `true` when every offered task was placed.
///
/// 当所有提交任务均被放置时返回 `true`。
pub fn is_complete(&self) -> bool { pub fn is_complete(&self) -> bool {
self.placed_count == self.offered_count self.placed_count == self.offered_count
} }
/// Returns the usage of one resource. /// Returns the usage of one resource.
///
/// 返回某个资源的使用情况。
pub fn usage_of(&self, resource: ResourceId) -> Option<&ResourceUsage> { pub fn usage_of(&self, resource: ResourceId) -> Option<&ResourceUsage> {
self.resource_usage self.resource_usage
.iter() .iter()

View File

@ -13,6 +13,12 @@ use std::collections::BTreeMap;
/// may be placed at most once: placing it again is an error rather than a /// 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 /// silent overwrite, because a schedule that contains two placements for one
/// task is not a schedule. /// task is not a schedule.
///
/// 一组任务放置。
///
/// 该计划按放置发生的顺序保存各项,并维护从任务到放置的索引,故按任务查找
/// 是直接的。一个任务至多放置一次:重复放置是错误而非静默覆盖,因为含有
/// 同一任务两处放置的集合已不是一份计划。
#[derive(Debug, Clone, PartialEq, Default)] #[derive(Debug, Clone, PartialEq, Default)]
pub struct Schedule { pub struct Schedule {
placements: Vec<Placement>, placements: Vec<Placement>,
@ -21,6 +27,8 @@ pub struct Schedule {
impl Schedule { impl Schedule {
/// Creates an empty schedule. /// Creates an empty schedule.
///
/// 创建一份空计划。
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
placements: Vec::new(), placements: Vec::new(),
@ -29,26 +37,36 @@ impl Schedule {
} }
/// Returns the placements in the order they were made. /// Returns the placements in the order they were made.
///
/// 返回按放置顺序排列的各项。
pub fn placements(&self) -> &[Placement] { pub fn placements(&self) -> &[Placement] {
&self.placements &self.placements
} }
/// Returns the number of placements. /// Returns the number of placements.
///
/// 返回放置的数量。
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.placements.len() self.placements.len()
} }
/// Returns `true` when there are no placements. /// Returns `true` when there are no placements.
///
/// 当没有放置时返回 `true`。
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.placements.is_empty() self.placements.is_empty()
} }
/// Returns `true` when a task has been placed. /// Returns `true` when a task has been placed.
///
/// 当某任务已被放置时返回 `true`。
pub fn is_placed(&self, task: TaskId) -> bool { pub fn is_placed(&self, task: TaskId) -> bool {
self.index.contains_key(&task.index()) self.index.contains_key(&task.index())
} }
/// Returns the placement of a task. /// Returns the placement of a task.
///
/// 返回某任务的放置。
pub fn placement_of(&self, task: TaskId) -> Option<&Placement> { pub fn placement_of(&self, task: TaskId) -> Option<&Placement> {
self.index self.index
.get(&task.index()) .get(&task.index())
@ -64,6 +82,14 @@ impl Schedule {
/// ///
/// Returns an error when the task is already placed, when the start time /// 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. /// is not finite, or when the placement falls outside the task's window.
///
/// 把某任务放置在某时刻。
///
/// 放置会对照该任务的窗口校验。任务的资源通过任务本身随放置一同记录,
/// 故本方法无需它们;容量检查由需要资源的
/// [`Schedule::validate_capacity`] 完成。
///
/// 该任务已被放置、起始时刻非有限,或放置落在任务窗口之外时返回错误。
pub fn place( pub fn place(
&mut self, &mut self,
task: &apsis_task::Task, task: &apsis_task::Task,
@ -107,6 +133,10 @@ impl Schedule {
/// Removes the placement of a task. /// Removes the placement of a task.
/// ///
/// Returns an error when the task is not placed. /// Returns an error when the task is not placed.
///
/// 移除某任务的放置。
///
/// 该任务未被放置时返回错误。
pub fn unplace(&mut self, task: TaskId) -> Result<Placement, ScheduleError> { pub fn unplace(&mut self, task: TaskId) -> Result<Placement, ScheduleError> {
let position = self.index.remove(&task.index()).ok_or_else(|| { let position = self.index.remove(&task.index()).ok_or_else(|| {
ScheduleError::new(ScheduleErrorKind::NotPlaced).with_detail(format!("{task}")) 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 // Every placement after the removed one shifts down by one, so the
// index must be rebuilt. The schedule is small enough that this is // index must be rebuilt. The schedule is small enough that this is
// cheaper than maintaining a linked structure. // cheaper than maintaining a linked structure.
// 被移除项之后的每项都前移一位,故索引须重建。计划规模足够小,
// 这比维护链式结构更划算。
self.rebuild_index(); self.rebuild_index();
Ok(removed) Ok(removed)
} }
/// Rebuilds the task to position index. /// Rebuilds the task to position index.
///
/// 重建从任务到位置的索引。
fn rebuild_index(&mut self) { fn rebuild_index(&mut self) {
self.index.clear(); self.index.clear();
for (position, placement) in self.placements.iter().enumerate() { for (position, placement) in self.placements.iter().enumerate() {
@ -128,6 +162,8 @@ impl Schedule {
} }
/// Returns the placements that use a resource, in time order. /// Returns the placements that use a resource, in time order.
///
/// 返回使用某资源的放置,按时间排序。
pub fn placements_on( pub fn placements_on(
&self, &self,
resource: ResourceId, resource: ResourceId,
@ -159,6 +195,12 @@ impl Schedule {
/// tasks on disjoint resources may legitimately overlap, so a caller that /// tasks on disjoint resources may legitimately overlap, so a caller that
/// wants to know whether a resource is overbooked should use /// wants to know whether a resource is overbooked should use
/// [`Schedule::validate_capacity`] instead. /// [`Schedule::validate_capacity`] instead.
///
/// 当两个不同任务的放置重叠时返回 `true`。
///
/// 这是计划与自身的比较,忽略资源。位于不相交资源上的两个任务本就可
/// 以合法重叠,故欲知某资源是否超订的调用方应改用
/// [`Schedule::validate_capacity`]。
pub fn has_overlapping_placements(&self) -> bool { pub fn has_overlapping_placements(&self) -> bool {
for (index, placement) in self.placements.iter().enumerate() { for (index, placement) in self.placements.iter().enumerate() {
if self.placements[..index] if self.placements[..index]
@ -172,6 +214,8 @@ impl Schedule {
} }
/// Returns the total placed duration, in seconds. /// Returns the total placed duration, in seconds.
///
/// 返回已放置的总时长,单位为秒。
pub fn scheduled_seconds(&self) -> f64 { pub fn scheduled_seconds(&self) -> f64 {
self.placements self.placements
.iter() .iter()
@ -182,6 +226,10 @@ impl Schedule {
/// Returns the earliest start of any placement. /// Returns the earliest start of any placement.
/// ///
/// Returns `None` when the schedule is empty. /// Returns `None` when the schedule is empty.
///
/// 返回所有放置中最早的开始时刻。
///
/// 计划为空时返回 `None`。
pub fn earliest_start(&self) -> Option<f64> { pub fn earliest_start(&self) -> Option<f64> {
self.placements self.placements
.iter() .iter()
@ -196,6 +244,10 @@ impl Schedule {
/// Returns the latest end of any placement. /// Returns the latest end of any placement.
/// ///
/// Returns `None` when the schedule is empty. /// Returns `None` when the schedule is empty.
///
/// 返回所有放置中最晚的结束时刻。
///
/// 计划为空时返回 `None`。
pub fn latest_end(&self) -> Option<f64> { pub fn latest_end(&self) -> Option<f64> {
self.placements self.placements
.iter() .iter()
@ -219,6 +271,17 @@ impl Schedule {
/// between boundaries. /// between boundaries.
/// ///
/// Returns an error describing the first violation. /// Returns an error describing the first violation.
///
/// 对照一组资源校验该计划。
///
/// 检查分两部分。其一,每处放置须与其任务的需求一致,这可捕获用另
/// 一份任务列表建成的计划。其二,对每个资源,任意瞬时的总需求不得超
/// 过容量。
///
/// 第二部分按时间序遍历各资源的放置边界,由于相邻边界之间总需求恒定,
/// 该遍历是精确的。
///
/// 返回描述首个违规的错误。
pub fn validate_capacity( pub fn validate_capacity(
&self, &self,
tasks: &[apsis_task::Task], tasks: &[apsis_task::Task],
@ -243,6 +306,7 @@ impl Schedule {
for resource in resources.resources() { for resource in resources.resources() {
// The boundaries at which the total demand can change. // The boundaries at which the total demand can change.
// 总需求可能发生变化的各边界。
let mut events: Vec<(f64, f64)> = Vec::new(); let mut events: Vec<(f64, f64)> = Vec::new();
for placement in &self.placements { for placement in &self.placements {
let Some(task) = tasks.iter().find(|task| task.id == placement.task) else { 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 // Sort by time, with a release before an acquisition at the same
// instant so that two placements that touch are not counted as // instant so that two placements that touch are not counted as
// overlapping. // overlapping.
// 按时间排序;同一瞬先释放后获取,使两个相接的放置不被计为
// 重叠。
events.sort_by(|first, second| { events.sort_by(|first, second| {
first first
.0 .0
@ -294,6 +360,11 @@ impl Schedule {
/// The evaluation reports the counts, the makespan, and the usage of each /// 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 /// resource. The peak demand of a resource is found by the same event
/// sweep that the capacity check uses, so the two agree. /// sweep that the capacity check uses, so the two agree.
///
/// 对照任务列表与资源集合评估该计划。
///
/// 评估给出各项计数、makespan 以及每个资源的使用情况。资源的峰值
/// 需求由与容量检查相同的事件扫掠求得,故二者一致。
pub fn evaluate( pub fn evaluate(
&self, &self,
tasks: &[apsis_task::Task], 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 /// 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 /// scheduler considered the tasks. A caller that wants to read the plan
/// in time order uses this. /// in time order uses this.
///
/// 返回按开始时刻排序的计划副本,原计划不变。
///
/// 计划按其构建顺序存储,即调度器考虑任务的顺序。欲按时间阅读该计
/// 划的调用方使用本方法。
pub fn in_time_order(&self) -> Vec<Placement> { pub fn in_time_order(&self) -> Vec<Placement> {
let mut ordered = self.placements.clone(); let mut ordered = self.placements.clone();
ordered.sort_by(|first, second| { ordered.sort_by(|first, second| {
@ -381,6 +457,11 @@ impl Schedule {
/// ///
/// Returns `None` when the resource is idle there. This is what a /// Returns `None` when the resource is idle there. This is what a
/// sequential scheduler asks when it looks for the next gap. /// sequential scheduler asks when it looks for the next gap.
///
/// 若某资源在给定瞬时忙碌,返回其变为空闲的时刻。
///
/// 该资源在彼处空闲时返回 `None`。顺序调度器寻找下一个空隙时正是
/// 询问这一点。
pub fn resource_busy_until( pub fn resource_busy_until(
&self, &self,
resource: ResourceId, resource: ResourceId,