From 500f9775db7637560d0ee2f0b1e093d550ad86b6 Mon Sep 17 00:00:00 2001 From: ApsisRS contributors Date: Fri, 2 Oct 2026 01:06:17 +0800 Subject: [PATCH] docs: bilingual comments for apsis-scheduler The layer is complete at three hundred and sixty two blocks across the resource, task, schedule, conflict and scheduler crates, including the swept search and the improvement pass. --- crates/ops/apsis-scheduler/src/greedy.rs | 32 ++++++ crates/ops/apsis-scheduler/src/improve.rs | 53 +++++++++ crates/ops/apsis-scheduler/src/planner.rs | 124 ++++++++++++++++++++++ crates/ops/apsis-scheduler/src/sweep.rs | 65 ++++++++++++ 4 files changed, 274 insertions(+) diff --git a/crates/ops/apsis-scheduler/src/greedy.rs b/crates/ops/apsis-scheduler/src/greedy.rs index 405b279..66a7af0 100644 --- a/crates/ops/apsis-scheduler/src/greedy.rs +++ b/crates/ops/apsis-scheduler/src/greedy.rs @@ -10,6 +10,11 @@ use apsis_task::{Task, TaskId}; /// A task that demands more of a resource than the resource has can never be /// scheduled on it, so this is checked before the search for a time. Returns /// `false` when a required resource does not exist. +/// +/// 当任务的每个资源都有容量容纳其需求时返回 `true`。 +/// +/// 需求超过某资源容量的任务永远无法在其上调度,故在搜索时刻之前先检查 +/// 此点。所需资源不存在时返回 `false`。 pub fn resources_have_capacity(task: &Task, resources: &ResourceSet) -> bool { task.requirements.iter().all(|requirement| { resources @@ -23,6 +28,10 @@ pub fn resources_have_capacity(task: &Task, resources: &ResourceSet) -> bool { /// /// The result is in the order the resources appear in the set. A resource /// that is not in the set is omitted. +/// +/// 给定一份计划及其任务集合,返回某瞬时各资源上的总需求。 +/// +/// 结果按资源在集合中的次序排列。不在集合中的资源被省略。 pub fn demand_at( schedule: &Schedule, tasks: &[Task], @@ -55,6 +64,11 @@ pub fn demand_at( /// the demand already placed on it leaves room for one more unit. This is /// the query a watermark scheduler makes when it looks for a satellite with /// spare capacity. +/// +/// 返回在某瞬空闲的资源标识符。 +/// +/// 资源空闲当且仅当其可用窗口覆盖该瞬,且其上已放置的需求还留有一个单 +/// 位的余量。这正是水位调度器寻找有余量卫星时所作的查询。 pub fn resources_free_at( schedule: &Schedule, tasks: &[Task], @@ -88,6 +102,11 @@ pub fn resources_free_at( /// The check is that every required resource is available for the whole /// duration and that the demand already placed on it, plus the new demand, /// stays within the capacity at every instant of the placement. +/// +/// 当某任务在某时刻的放置可行时返回 `true`。 +/// +/// 检查内容为:每个所需资源在整个时长上可用,且其上已放置的需求加上 +/// 新需求,在放置的每个瞬时都不超过容量。 pub fn feasible_at( task: &Task, tasks: &[Task], @@ -106,6 +125,12 @@ pub fn feasible_at( /// placed task while collecting the boundaries, and the one inside that sums /// the demand over every placement covering a boundary. The boundary /// arithmetic is unchanged, so the answer is the same as the scans give. +/// +/// 使用调用方已构建一次的需求索引,判断某任务在某时刻的放置是否可行。 +/// +/// 该索引取代了对任务列表的两次线性扫描:收集边界时查找已放置任务的那 +/// 次,以及对每个覆盖该边界的放置求和需求的那次。边界运算本身不变,故 +/// 结果与扫描给出的一致。 pub(crate) fn feasible_at_indexed( task: &Task, schedule: &Schedule, @@ -128,6 +153,8 @@ pub(crate) fn feasible_at_indexed( // overlap it. Because the demand of each placement is constant over // its own interval, it is enough to test the boundaries of the // overlapping placements, where the total is greatest. + // 新任务的需求须与与其重叠的放置并存。由于每个放置的需求在其 + // 自身区间内恒定,只需检查重叠放置的边界,即总量最大之处。 let mut boundaries: Vec = vec![start]; for placement in schedule.placements() { if crate::sweep::demand_on(index, placement.task, resource.id).is_none() { @@ -159,6 +186,11 @@ pub(crate) fn feasible_at_indexed( /// has, when a resource it requires does not exist, or when its window is /// shorter than its duration. These cannot be repaired by rescheduling and /// are worth reporting separately from the tasks that were merely left out. +/// +/// 返回某组资源完全无法容纳的任务标识符。 +/// +/// 任务不可行的情形有:需求超过某资源容量;所需资源不存在;窗口短于其 +/// 时长。这些无法通过重排修复,值得与那些只是被排除的任务分开报告。 pub fn infeasible_tasks(tasks: &[Task], resources: &ResourceSet) -> Vec { tasks .iter() diff --git a/crates/ops/apsis-scheduler/src/improve.rs b/crates/ops/apsis-scheduler/src/improve.rs index e58f8b3..f28f92e 100644 --- a/crates/ops/apsis-scheduler/src/improve.rs +++ b/crates/ops/apsis-scheduler/src/improve.rs @@ -32,20 +32,31 @@ use apsis_schedule::Schedule; use apsis_task::{Task, TaskId, TaskState}; /// The settings of the improvement pass. +/// +/// 改进轮的设置。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct ImprovementSettings { /// Whether the pass runs. + /// + /// 该轮是否运行。 pub enabled: bool, /// The greatest number of blocking placements to try per task. /// /// Each attempt costs two feasibility searches. A small bound keeps the /// pass predictable; a larger one finds more placements on a crowded /// schedule at proportional cost. + /// + /// 每个任务最多尝试的阻塞放置数。 + /// + /// 每次尝试耗费两次可行性搜索。较小的界使该轮表现可预期;较大的界 + /// 能在拥挤的计划上找到更多放置,代价相应增加。 pub max_attempts_per_task: usize, } impl Default for ImprovementSettings { /// Returns settings that run the pass with four attempts per task. + /// + /// 返回每任务尝试四次的设置。 fn default() -> Self { Self { enabled: true, @@ -56,6 +67,8 @@ impl Default for ImprovementSettings { impl ImprovementSettings { /// Creates settings with the given bound. + /// + /// 以给定界创建设置。 pub const fn new(max_attempts_per_task: usize) -> Self { Self { enabled: true, @@ -64,6 +77,8 @@ impl ImprovementSettings { } /// Returns settings that do not run the pass. + /// + /// 返回不运行该轮的设置。 pub const fn disabled() -> Self { Self { enabled: false, @@ -73,21 +88,31 @@ impl ImprovementSettings { } /// What the improvement pass changed. +/// +/// 改进轮所作的改变。 #[derive(Debug, Clone, PartialEq, Default)] pub struct ImprovementOutcome { /// The identifiers of the tasks the pass placed. + /// + /// 该轮所放置的任务标识符。 pub placed: Vec, /// The identifiers of the tasks the pass moved to make room. + /// + /// 该轮为腾出空隙而移动的任务标识符。 pub moved: Vec, } impl ImprovementOutcome { /// Returns the number of tasks the pass placed. + /// + /// 返回该轮所放置的任务数。 pub fn placed_count(&self) -> usize { self.placed.len() } /// Returns `true` when the pass changed nothing. + /// + /// 当该轮未作任何改变时返回 `true`。 pub fn is_empty(&self) -> bool { self.placed.is_empty() && self.moved.is_empty() } @@ -101,6 +126,14 @@ impl ImprovementOutcome { /// assignment. The schedule is modified in place. /// /// Returns a record of what the pass placed and moved. +/// +/// 通过搬迁阻塞它们的放置,尝试安排被推迟的任务。 +/// +/// `deferred` 是贪心轮无法放置的任务,按考虑顺序排列。`states` 按标识符 +/// 索引保存每个任务的状态并就地更新,故调用方看到的是最终指派。该计划 +/// 亦被就地修改。 +/// +/// 返回记录该轮所放置与所移动内容的报告。 pub fn improve( tasks: &[Task], resources: &ResourceSet, @@ -119,6 +152,7 @@ pub fn improve( continue; }; // A task that is not deferred has been placed by an earlier step. + // 非被推迟的任务已由先前步骤放置。 if schedule.is_placed(*id) { continue; } @@ -164,6 +198,11 @@ pub fn improve( /// /// Only these can block it: a placement on a resource the task does not need /// consumes capacity that the task was never going to use. +/// +/// 返回与某等待任务共用资源的已放置任务,按时间排序。 +/// +/// 只有这些能阻塞它:某个该任务并不需要的资源上的放置,消耗的是它本就 +/// 不会用到的容量。 fn blocking_tasks(waiting: &Task, tasks: &[Task], schedule: &Schedule) -> Vec { let mut blockers: Vec<(f64, TaskId)> = schedule .placements() @@ -196,6 +235,11 @@ fn blocking_tasks(waiting: &Task, tasks: &[Task], schedule: &Schedule) -> Vec { if schedule.place(blocker, blocker_new_start).is_ok() { @@ -232,6 +279,9 @@ fn relocate_pair( // can only come from the blocker being placed already, which it // is not. Restore the original arrangement and report failure // rather than leaving a half applied move. + // 搜索已判定该放置可行,故此处的失败只可能源于阻塞者已被 + // 放置,而它并未被放置。恢复原安排并报告失败,以免留下 + // 半完成的移动。 let _ = schedule.unplace(waiting.id); let _ = schedule.place(blocker, blocker_start); false @@ -239,6 +289,7 @@ fn relocate_pair( None => { // The blocker has nowhere to go, so the move is abandoned and // the original arrangement restored. + // 阻塞者无处可去,故放弃该次移动并恢复原安排。 let _ = schedule.unplace(waiting.id); let _ = schedule.place(blocker, blocker_start); false @@ -248,6 +299,8 @@ fn relocate_pair( /// Updates the recorded state of a task, ignoring an identifier outside the /// state list. +/// +/// 更新某任务所记录的状态;标识符不在状态列表之内时忽略。 fn set_state(states: &mut [TaskState], id: TaskId, state: TaskState) { if let Some(slot) = states.get_mut(id.index()) { *slot = state; diff --git a/crates/ops/apsis-scheduler/src/planner.rs b/crates/ops/apsis-scheduler/src/planner.rs index 5ef8d94..467a7b2 100644 --- a/crates/ops/apsis-scheduler/src/planner.rs +++ b/crates/ops/apsis-scheduler/src/planner.rs @@ -13,53 +13,84 @@ use apsis_task::{PrecedenceGraph, Task, TaskId, TaskState, task_order}; /// information an operator needs to see. It is separate from the schedule /// because a task that was not placed has no placement but still has an /// outcome worth reporting. +/// +/// 规划过程中某个任务的结果。 +/// +/// 它把任务与规划器所指定的状态配对,这是操作者需要看到的信息。它与 +/// 计划分开,因为未放置的任务虽无放置,却仍有一份值得报告的结果。 #[derive(Debug, Clone, PartialEq)] pub struct PlanOutcome { /// The task. + /// + /// 该任务。 pub task: TaskId, /// The state the planner assigned. + /// + /// 规划器所指定的状态。 pub state: TaskState, } impl PlanOutcome { /// Returns `true` when the task was placed. + /// + /// 当该任务已被放置时返回 `true`。 pub fn is_scheduled(&self) -> bool { self.state.is_scheduled() } /// Returns `true` when the task could not be placed for a structural /// reason, rather than being left out for want of time. + /// + /// 当该任务因结构性原因无法放置、而非因时间不足被排除时返回 `true`。 pub fn is_infeasible(&self) -> bool { matches!(self.state, TaskState::Infeasible { .. }) } } /// The result of planning. +/// +/// 规划的结果。 #[derive(Debug, Clone, PartialEq)] pub struct SchedulingResult { /// The schedule that was produced. + /// + /// 所产出的计划。 pub schedule: Schedule, /// The outcome of every task, in the order the planner considered them. /// /// The order is the order of the task list the planner was given, not the /// order in which the planner placed them, so that a caller can index the /// outcomes by the position of the task in its own list. + /// + /// 每个任务的结果,按规划器考虑它们的顺序排列。 + /// + /// 该顺序是规划器所获任务列表的顺序,而非其放置的顺序,故调用方 + /// 可按任务在自身列表中的位置索引结果。 pub outcomes: Vec, /// The identifiers of the tasks that the improvement pass placed. /// /// These tasks were left out by the greedy pass and placed afterwards by /// moving other placements. They are also recorded as scheduled in the /// outcomes. + /// + /// 改进轮所放置的任务标识符。 + /// + /// 这些任务被贪心轮排除,其后通过移动其他放置而被安排。它们在 + /// 结果中同样被记为已调度。 pub improved: Vec, } impl SchedulingResult { /// Returns the outcome of a task. + /// + /// 返回某任务的结果。 pub fn outcome_of(&self, task: TaskId) -> Option<&PlanOutcome> { self.outcomes.iter().find(|outcome| outcome.task == task) } /// Returns the identifiers of the tasks that were placed. + /// + /// 返回已放置任务的标识符。 pub fn scheduled(&self) -> Vec { self.outcomes .iter() @@ -69,6 +100,8 @@ impl SchedulingResult { } /// Returns the identifiers of the tasks that were left out. + /// + /// 返回被排除任务的标识符。 pub fn deferred(&self) -> Vec { self.outcomes .iter() @@ -78,6 +111,8 @@ impl SchedulingResult { } /// Returns the identifiers of the tasks that cannot be placed at all. + /// + /// 返回完全无法放置的任务的标识符。 pub fn infeasible(&self) -> Vec { self.outcomes .iter() @@ -87,6 +122,8 @@ impl SchedulingResult { } /// Returns `true` when every offered task was placed. + /// + /// 当所有提交任务均被放置时返回 `true`。 pub fn is_complete(&self) -> bool { self.outcomes.iter().all(PlanOutcome::is_scheduled) } @@ -95,6 +132,11 @@ impl SchedulingResult { /// /// A plan that leaves a mandatory task unplaced is not usable, however /// much optional work it found room for. + /// + /// 当所有强制任务均被放置时返回 `true`。 + /// + /// 留下强制任务未放置的计划不可用,无论它为多少可选工作腾出了 + /// 空间。 pub fn honours_mandatory(&self, tasks: &[Task]) -> bool { tasks .iter() @@ -107,6 +149,11 @@ impl SchedulingResult { /// A constraint is satisfied when the second task starts at or after the /// end of the first. A constraint whose tasks are not both placed is /// trivially satisfied, because nothing has been promised about them. + /// + /// 当所有优先约束都得到满足时返回 `true`。 + /// + /// 约束被满足,指第二个任务的起点不早于第一个的终点。两个任务未 + /// 同时放置的约束平凡地满足,因为对它们未作任何承诺。 pub fn honours_precedence(&self, graph: &PrecedenceGraph) -> bool { self.outcomes .iter() @@ -129,6 +176,11 @@ impl SchedulingResult { /// The planner holds the settings of the search. The settings are separated /// from the schedule so that the same planner can be applied to many task /// sets with the same policy. +/// +/// 贪心规划器。 +/// +/// 该规划器持有搜索的设置。设置与计划分开,故同一个规划器可按同一策略 +/// 应用于许多任务集。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct Planner { /// Whether to place mandatory tasks before the others regardless of @@ -138,14 +190,23 @@ pub struct Planner { /// weighted value is low: the optional tasks can use whatever time is /// left, while the reverse order could consume the only window the /// mandatory task had. + /// + /// 是否不论价值高低都先放置强制任务。 + /// + /// 强制任务必须执行,故即便其加权价值很低也值得先放置:可选任务 + /// 可以利用剩下的时间,而相反的顺序可能耗尽强制任务仅有的那个窗口。 pub mandatory_first: bool, /// The settings of the improvement pass. + /// + /// 改进轮的设置。 pub improvement: ImprovementSettings, } impl Default for Planner { /// Returns a planner that places mandatory tasks first and runs the /// improvement pass. + /// + /// 返回一个先放置强制任务并运行改进轮的规划器。 fn default() -> Self { Self { mandatory_first: true, @@ -159,6 +220,11 @@ impl Planner { /// /// The defaults are the ones [`Planner::default`] uses: mandatory tasks /// first and the improvement pass enabled with four attempts per task. + /// + /// 以默认设置创建规划器。 + /// + /// 默认值即 [`Planner::default`] 所用者:先放置强制任务,改进轮 + /// 启用且每任务尝试四次。 pub const fn new() -> Self { Self { mandatory_first: true, @@ -167,12 +233,16 @@ impl Planner { } /// Returns a copy of this planner with the mandatory placement rule set. + /// + /// 返回设置了强制放置规则的规划器副本。 pub const fn with_mandatory_first(mut self, mandatory_first: bool) -> Self { self.mandatory_first = mandatory_first; self } /// Returns a copy of this planner with the improvement pass configured. + /// + /// 返回配置了改进轮的规划器副本。 pub const fn with_improvement(mut self, improvement: ImprovementSettings) -> Self { self.improvement = improvement; self @@ -188,6 +258,15 @@ impl Planner { /// graph, using the value order to break ties. That places a task as /// early as its constraints allow and the most valuable one first among /// the tasks that are free. + /// + /// 返回规划器考虑任务的顺序。 + /// + /// 无优先关系图时,顺序是先强制任务(若规划器如此配置),再按加权 + /// 价值递减,平局按截止时刻与标识符打破。 + /// + /// 有优先关系图时,顺序是该图的拓扑序,并以价值次序打破平局。这 + /// 使任务在其约束允许的最早时刻被放置,而在自由的任务中优先放置 + /// 价值最高者。 pub fn order(&self, tasks: &[Task]) -> Vec { self.order_with_precedence(tasks, None) } @@ -200,6 +279,12 @@ impl Planner { /// interpretation. The caller should validate the graph before /// planning; the order is a fallback so that planning still produces /// something rather than failing outright. + /// + /// 返回规划器考虑任务的顺序,并遵从给定的优先关系图。 + /// + /// 约束含环的图会原样产生价值次序,因为这些约束此时没有可满足的 + /// 解释。调用方应在规划前校验该图;此处的次序是一个回退,使规划 + /// 仍能产出结果而非直接失败。 pub fn order_with_precedence( &self, tasks: &[Task], @@ -239,6 +324,15 @@ impl Planner { /// The schedule that is returned is feasible by construction, because /// each placement is checked against the capacity before it is made. The /// caller may confirm that with `apsis-conflict`. + /// + /// 在一组资源上规划一组任务。 + /// + /// 每个任务按规划器所选的顺序被考虑一次,被放置在其全部资源恰好同时 + /// 空闲的最早时刻。无法放置的任务被报告为被推迟;不存在任何可行 + /// 放置的则报告为不可行。 + /// + /// 返回的计划按构造即可行,因为每处放置在做下之前都对照容量校验 + /// 过。调用方可用 `apsis-conflict` 复核。 pub fn schedule(&self, tasks: &[Task], resources: &ResourceSet) -> SchedulingResult { self.plan(tasks, resources, None) } @@ -256,6 +350,16 @@ impl Planner { /// that the pass does not consider. A caller that needs both should /// improve the plan and then verify it with /// [`SchedulingResult::honours_precedence`]. + /// + /// 遵从优先关系图规划一组任务。 + /// + /// 任务仅在其所有前驱均已放置之后才被放置,故其起点搜索不早于 + /// 前驱中最晚的结束时刻。无法放置的前驱会使其后继同样被推迟, + /// 因为该约束无法满足。 + /// + /// 改进轮不作用于带优先约束的计划,因为移动某处放置可能破坏该轮 + /// 并不考虑的约束。两者皆需的调用方应先改进计划,再用 + /// [`SchedulingResult::honours_precedence`] 复核。 pub fn schedule_with_precedence( &self, tasks: &[Task], @@ -266,6 +370,8 @@ impl Planner { } /// The shared planning routine. + /// + /// 共享的规划例程。 fn plan( &self, tasks: &[Task], @@ -275,11 +381,13 @@ impl Planner { let mut schedule = Schedule::new(); // The state of every task, indexed by identifier, so that a // predecessor's placement can be looked up by index. + // 各任务的状态,按标识符索引,使前驱的放置可按下标查得。 let mut states = vec![TaskState::Pending; tasks.len()]; let mut outcomes = Vec::with_capacity(tasks.len()); let mut order: Vec = self.order_with_precedence(tasks, precedence); // A deferred task is one whose search failed, as opposed to one whose // structural preconditions failed. + // 被推迟的任务指搜索失败者,区别于结构性前置条件失败者。 let mut deferred: Vec = Vec::new(); let infeasible = infeasible_tasks(tasks, resources); @@ -311,6 +419,7 @@ impl Planner { // A task may not start before the latest end among the tasks that // must precede it. + // 任务的起点不得早于其前驱中最晚的结束时刻。 let earliest = match self.precedence_floor(id, tasks, precedence, &schedule, &states) { Ok(floor) => floor, Err(reason) => { @@ -327,6 +436,7 @@ impl Planner { // The task with the floor applied, so that the search respects // the constraints. + // 施加该下界后的任务,使搜索遵从各约束。 let constrained = match earliest { Some(floor) if floor > task.earliest_start => { let mut copy = task.clone(); @@ -373,6 +483,8 @@ impl Planner { // The improvement pass runs only for a plan without precedence // constraints, for the reason given on `schedule_with_precedence`. + // 改进轮只作用于无优先约束的计划,原因见 + // `schedule_with_precedence`。 let improved = if precedence.is_some() { Vec::new() } else { @@ -387,6 +499,8 @@ impl Planner { // The outcomes that the pass changed must be updated, and the // order of the outcomes follows the task list so that a caller // can index them by position. + // 该轮改动的结果必须更新,且结果次序遵循任务列表,使调用 + // 方可按位置索引。 for id in &outcome.placed { if let Some(slot) = outcomes.iter_mut().find(|entry| entry.task == *id) { let start = schedule @@ -400,6 +514,8 @@ impl Planner { // Reorder the outcomes to follow the task list, which is the order a // caller expects when it indexes by its own position. + // 将结果重排为遵循任务列表,即调用方按自身位置索引时所期望的 + // 次序。 outcomes.sort_by_key(|outcome| outcome.task.index()); SchedulingResult { @@ -414,6 +530,11 @@ impl Planner { /// Returns `Ok(None)` when there is no constraint, `Ok(Some(t))` when the /// task may start no earlier than `t`, and `Err` when a predecessor was /// not placed, which makes the constraint unsatisfiable. + /// + /// 给定其前驱,返回某任务可有的最早起点。 + /// + /// 无约束时返回 `Ok(None)`;任务起点不早于 `t` 时返回 `Ok(Some(t))`; + /// 某前驱未被放置致使约束不可满足时返回 `Err`。 fn precedence_floor( &self, id: TaskId, @@ -429,6 +550,7 @@ impl Planner { for predecessor in graph.predecessors_of(id) { // A predecessor that was deferred or is infeasible cannot be // waited for. + // 被推迟或不可行的前驱无从等待。 let placed = schedule.placement_of(predecessor); let Some(placement) = placed else { let reason = match states.get(predecessor.index()) { @@ -445,6 +567,8 @@ impl Planner { } /// Records the state of a task, ignoring an identifier outside the list. +/// +/// 记录某任务的状态;标识符不在列表之内时忽略。 fn set_state(states: &mut [TaskState], id: TaskId, state: TaskState) { if let Some(slot) = states.get_mut(id.index()) { *slot = state; diff --git a/crates/ops/apsis-scheduler/src/sweep.rs b/crates/ops/apsis-scheduler/src/sweep.rs index d97acfd..a4efe80 100644 --- a/crates/ops/apsis-scheduler/src/sweep.rs +++ b/crates/ops/apsis-scheduler/src/sweep.rs @@ -31,6 +31,11 @@ use apsis_task::TaskId; /// /// It matches the tolerance in [`feasible_at`], so the sweep and the direct /// test draw the same line at a boundary. +/// +/// 比较负载与容量时允许的余量。 +/// +/// 它与 [`feasible_at`] 中的容差一致,故扫掠与直接测试在边界处划同一条 +/// 线。 const CAPACITY_SLACK: f64 = 1e-9; /// Returns the earliest time at which a task can be placed. @@ -43,6 +48,14 @@ const CAPACITY_SLACK: f64 = 1e-9; /// the availability is constant between them too. /// /// Returns `None` when the task cannot be placed anywhere in its window. +/// +/// 返回某任务可被放置的最早时刻。 +/// +/// 最早可行起点总是某个小候选集合之一:任务窗口的起点、其某资源可用窗口 +/// 的起点,或其某资源上已有放置的结束时刻。可行性在其他任何瞬时都不会 +/// 改变,因为各资源的需求在这些事件之间恒定,可用性亦然。 +/// +/// 该任务在其窗口内无处可放时返回 `None`。 pub fn earliest_feasible_start( task: &Task, tasks: &[Task], @@ -54,6 +67,7 @@ pub fn earliest_feasible_start( } if task.requirements.is_empty() { // A task with no requirements can start as soon as its window opens. + // 无需求的任务可在其窗口开启时即开始。 return Some(task.earliest_start); } let latest_start = task.latest_end - task.duration; @@ -63,9 +77,14 @@ pub fn earliest_feasible_start( // demand by scanning the task list at each of those visits would make // deciding one task quadratic in the plan. The map is built once per // task, which is linear, and shared by every visit. + // 各任务的需求,按标识符一次性建好索引。扫掠会访问每个所需资源上 + // 的每处放置,若每次访问都扫描任务列表来读需求,判定一个任务就会 + // 对整个计划呈二次。该索引每个任务构建一次,是线性的,并由所有 + // 访问共享。 let demands = demand_index(tasks); // The sweep, confirmed by the direct test. + // 扫掠,其结果由直接测试确认。 if let Some(candidate) = swept_earliest_start(task, schedule, resources, latest_start, &demands) { if feasible_at_indexed(task, schedule, resources, candidate, &demands) { @@ -76,12 +95,18 @@ pub fn earliest_feasible_start( // The enumeration, which is exact. It runs when the sweep could not // produce a confirmed answer, which is when the task does not fit at all // or when rounding made the two disagree. + // 枚举,它是精确的。当扫掠无法给出经确认的答案时运行,即该任务 + // 完全放不下,或舍入使二者不一致时。 enumerate_earliest_start(task, schedule, resources, latest_start, &demands) } /// Returns the earliest start by testing every candidate in turn. /// /// This is the original formulation, kept as the fallback for the sweep. +/// +/// 依次测试每个候选,返回最早起点。 +/// +/// 这是最初的表述,保留为扫掠的回退。 fn enumerate_earliest_start( task: &Task, schedule: &Schedule, @@ -122,6 +147,11 @@ fn enumerate_earliest_start( /// The intervals returned by [`starts_fitting_on`] for each resource are /// intersected, and the first interval that lies inside the task's window /// gives the answer. +/// +/// 由扫掠所得的负载曲线导出最早起点。 +/// +/// [`starts_fitting_on`] 为各资源返回的区间被求交,落在该任务窗口内的 +/// 第一个区间即给出答案。 fn swept_earliest_start( task: &Task, schedule: &Schedule, @@ -158,6 +188,11 @@ fn swept_earliest_start( /// availability window and the demand already placed on the resource never /// exceeds the capacity less the new demand. The result is sorted and the /// intervals are disjoint. +/// +/// 返回可使给定时长与需求的放置落在某资源上的起点区间。 +/// +/// 当整个放置位于某个可用窗口内、且资源上已放置的需求始终不超过容量减去 +/// 新需求时,该起点被计入结果。结果有序且各区间互不相交。 fn starts_fitting_on( duration: f64, demand: f64, @@ -172,6 +207,7 @@ fn starts_fitting_on( for (from, to) in subtract(&blocked, window.start(), window.end()) { // The placement has to fit inside, so the last start is the end // of the free interval less the duration. + // 放置须容纳于其中,故最晚起点为该空闲区间的终点减去时长。 let last_start = to - duration; if last_start >= from { result.push((from, last_start)); @@ -186,6 +222,11 @@ fn starts_fitting_on( /// The intervals are where the demand already placed on the resource exceeds /// the capacity less `demand`. They are computed with a single sweep over the /// placement boundaries, and are sorted and disjoint. +/// +/// 返回某资源无余量容纳某需求的区间。 +/// +/// 这些区间即资源上已放置的需求超过容量减去 `demand` 的时段。它们由 +/// 对各放置边界的一次扫掠算出,有序且互不相交。 fn blocked_intervals( demand: f64, resource_id: ResourceId, @@ -197,6 +238,8 @@ fn blocked_intervals( // The load profile changes only where a placement starts or ends, so the // sweep needs only those two instants per placement. + // 负载曲线只在某处放置开始或结束时改变,故扫掠每个放置只需那两个 + // 瞬时。 let mut events: Vec<(f64, f64)> = Vec::new(); for placement in schedule.placements() { let Some(placed) = demand_on(demands, placement.task, resource_id) else { @@ -229,12 +272,15 @@ fn blocked_intervals( while index < events.len() { let time = events[index].0; // The load is constant on the interval that ends at this instant. + // 负载在以该瞬时结束的区间上恒定。 if load > limit { opened.get_or_insert(time); } // Apply every change at this instant at once, so the load compared // above belongs to the interval before it and the one after belongs // to the next comparison. + // 一次性施加该瞬时的全部变化,故上面比较的负载属于其前的区间, + // 其后的负载则用于下一次比较。 let mut next = index; while next < events.len() && events[next].0 == time { load += events[next].1; @@ -249,6 +295,8 @@ fn blocked_intervals( } // A run that never closes extends past every placement, so it is // recorded as open ended and swallows the rest of the horizon. + // 始终不闭合的区段延伸越过所有放置,故记为开区间,吞没时域的 + // 其余部分。 if let Some(start) = opened { blocked.push((start, f64::INFINITY)); } @@ -261,9 +309,16 @@ fn blocked_intervals( /// vector indexed by it rather than in a map. Reading one is then an array /// index rather than a hash, and a hash of a small integer costs more than /// the scan it replaces. +/// +/// 各任务在各资源上的需求,按任务标识符索引。 +/// +/// 任务标识符是小的稠密整数,故需求存于以其为下标的向量而非映射中。读取 +/// 时即数组下标而非哈希,而哈希一个小整数的代价高于它所取代的扫描。 pub(crate) type DemandIndex = Vec>>; /// Builds a [`DemandIndex`] in one pass over the task list. +/// +/// 由任务列表一趟构建 [`DemandIndex`]。 pub(crate) fn demand_index(tasks: &[Task]) -> DemandIndex { let highest = tasks.iter().map(|task| task.id.index()).max().unwrap_or(0); let mut index: DemandIndex = (0..=highest).map(|_| None).collect(); @@ -282,6 +337,8 @@ pub(crate) fn demand_index(tasks: &[Task]) -> DemandIndex { } /// Returns the demand a placed task puts on a resource, if any. +/// +/// 返回某已放置任务在某资源上的需求,若有。 pub(crate) fn demand_on(index: &DemandIndex, task: TaskId, resource: ResourceId) -> Option { index .get(task.index()) @@ -293,6 +350,10 @@ pub(crate) fn demand_on(index: &DemandIndex, task: TaskId, resource: ResourceId) /// Returns the pieces of an interval that no blocked interval covers. /// /// Both inputs are sorted and disjoint. The result is sorted and disjoint. +/// +/// 返回某区间中未被任何阻塞区间覆盖的部分。 +/// +/// 两个输入均有序且互不相交,结果亦然。 fn subtract(blocked: &[(f64, f64)], from: f64, to: f64) -> Vec<(f64, f64)> { let mut pieces = Vec::new(); let mut cursor = from; @@ -320,6 +381,10 @@ fn subtract(blocked: &[(f64, f64)], from: f64, to: f64) -> Vec<(f64, f64)> { /// Returns the intersection of two sorted, disjoint lists of intervals. /// /// The result is sorted and disjoint. +/// +/// 返回两份有序且互不相交的区间列表之交。 +/// +/// 结果有序且互不相交。 fn intersect(left: &[(f64, f64)], right: &[(f64, f64)]) -> Vec<(f64, f64)> { let mut result = Vec::new(); let mut i = 0;