From c442acc6e5f5e56954cfbabb0bbcdf9e2fc059a0 Mon Sep 17 00:00:00 2001 From: ApsisRS contributors Date: Fri, 2 Oct 2026 01:24:45 +0800 Subject: [PATCH] docs: bilingual comments for handover and topology --- crates/network/apsis-handover/src/decision.rs | 59 ++++++++++++ crates/network/apsis-handover/src/policy.rs | 85 +++++++++++++++++ crates/network/apsis-topology/src/graph.rs | 95 +++++++++++++++++++ crates/network/apsis-topology/src/series.rs | 81 ++++++++++++++++ 4 files changed, 320 insertions(+) diff --git a/crates/network/apsis-handover/src/decision.rs b/crates/network/apsis-handover/src/decision.rs index 2f82a53..af6f26d 100644 --- a/crates/network/apsis-handover/src/decision.rs +++ b/crates/network/apsis-handover/src/decision.rs @@ -4,9 +4,13 @@ use crate::error::{HandoverError, HandoverErrorKind}; use crate::policy::HandoverPolicy; /// A satellite that could serve the terminal. +/// +/// 一颗可能服务终端的卫星。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct Candidate { /// The satellite identifier. + /// + /// 卫星标识符。 pub satellite: usize, /// The quality of the link to this satellite. /// @@ -14,17 +18,29 @@ pub struct Candidate { /// carrier to noise ratio, or any other measure of how good the link is. /// The policy thresholds are expressed in the same units. The value must /// be finite. + /// + /// 到该卫星链路的质量。 + /// + /// 其含义由调用方定义:仰角、接收功率、载噪比,或任何其他衡量链路 + /// 好坏的度量。策略阈值以相同单位表达。该值必须有限。 pub quality: f64, /// The time until this satellite stops being available, in seconds. /// /// This is the remaining time in the access window. It lets the policy /// prefer a satellite that will last, and it is reported so a caller can /// plan the next decision. + /// + /// 到该卫星不再可用所剩的时间,单位为秒。 + /// + /// 这是可见性窗口中的剩余时间。它使策略可以偏好将持续更久的卫星, + /// 并被报告出来以便调用方规划下一步决策。 pub remaining_seconds: f64, } impl Candidate { /// Creates a candidate. + /// + /// 创建一个候选。 pub const fn new(satellite: usize, quality: f64, remaining_seconds: f64) -> Self { Self { satellite, @@ -34,6 +50,8 @@ impl Candidate { } /// Returns `true` when the candidate is usable. + /// + /// 当该候选可用时返回 `true`。 pub fn is_valid(&self) -> bool { self.quality.is_finite() && self.remaining_seconds.is_finite() @@ -42,32 +60,54 @@ impl Candidate { } /// What the terminal should do at one instant. +/// +/// 终端在某一瞬时应做的动作。 #[derive(Debug, Clone, Copy, PartialEq)] pub enum HandoverDecision { /// Keep the current server. + /// + /// 保持当前服务星。 Stay, /// Switch from one satellite to another. + /// + /// 从一颗卫星切换到另一颗。 HandOver { /// The satellite being left. + /// + /// 被离开的卫星。 from: usize, /// The satellite being taken up. + /// + /// 被接替的卫星。 to: usize, /// The quality of the satellite being left. + /// + /// 被离开卫星的质量。 from_quality: f64, /// The quality of the satellite being taken up. + /// + /// 被接替卫星的质量。 to_quality: f64, }, /// Establish service for the first time. + /// + /// 首次建立服务。 Acquire { /// The satellite to use. + /// + /// 要使用的卫星。 satellite: usize, }, /// Give up service, because no candidate meets the minimum quality. + /// + /// 放弃服务,因为没有候选满足最低质量。 Drop, } impl HandoverDecision { /// Returns `true` when the decision changes the serving satellite. + /// + /// 当该决策改变服务卫星时返回 `true`。 pub const fn is_change(self) -> bool { matches!(self, Self::HandOver { .. } | Self::Acquire { .. }) } @@ -76,6 +116,11 @@ impl HandoverDecision { /// /// Returns `None` for [`HandoverDecision::Drop`] and for /// [`HandoverDecision::Stay`] with no current server. + /// + /// 返回决策之后终端所服务的卫星。 + /// + /// 对 [`HandoverDecision::Drop`] 以及没有当前服务星的 + /// [`HandoverDecision::Stay`] 返回 `None`。 pub const fn resulting_satellite(self) -> Option { match self { Self::HandOver { to, .. } => Some(to), @@ -86,6 +131,8 @@ impl HandoverDecision { } /// Returns a short name for the decision. + /// + /// 返回该决策的简称。 pub const fn name(self) -> &'static str { match self { Self::Stay => "stay", @@ -97,6 +144,8 @@ impl HandoverDecision { } /// Validates a candidate list against the policy. +/// +/// 针对策略校验候选列表。 fn validate(policy: &HandoverPolicy, candidates: &[Candidate]) -> Result<(), HandoverError> { policy.validate()?; if candidates.is_empty() { @@ -113,6 +162,8 @@ fn validate(policy: &HandoverPolicy, candidates: &[Candidate]) -> Result<(), Han } // A duplicate would make the choice between two entries ambiguous // and would let a caller weight a satellite by listing it twice. + // 重复项会使两项之间的选择变得含糊,并让调用方通过列出两次来 + // 加重某卫星的权重。 if candidates[..index] .iter() .any(|earlier| earlier.satellite == candidate.satellite) @@ -181,6 +232,8 @@ pub fn decide_with_history( } /// Applies the rules with an optional dwell time. +/// +/// 以可选驻留时间施加各条规则。 fn decide_internal( policy: &HandoverPolicy, current: Option, @@ -201,6 +254,7 @@ fn decide_internal( // Rule 1: no current server, so acquire the best that meets the // minimum, or drop. + // 规则 1:没有当前服务星,故获取满足最低质量的最优者,否则放弃。 let Some(current_satellite) = current else { return Ok(if best.quality >= policy.minimum_quality { HandoverDecision::Acquire { @@ -213,6 +267,7 @@ fn decide_internal( // Rule 2: the current server is no longer a candidate, so it has become // unavailable and the terminal must move. + // 规则 2:当前服务星不再是候选,故它已不可用,终端必须迁移。 let Some(current_candidate) = current_candidate else { return Ok(if best.quality >= policy.minimum_quality { HandoverDecision::HandOver { @@ -228,10 +283,13 @@ fn decide_internal( // Rule 3: the current server has fallen below the minimum, so abandon // it. This is a forced handover and the dwell time does not apply. + // 规则 3:当前服务星已跌至最低质量以下,故放弃它。这是强制切换, + // 驻留时间不适用。 if current_candidate.quality < policy.minimum_quality { if best.satellite == current_satellite { // The best candidate is the current server, which is too weak, // so there is nothing to move to. + // 最优候选就是当前服务星,而它太弱,故无处可迁。 return Ok(HandoverDecision::Drop); } return Ok(if best.quality >= policy.minimum_quality { @@ -248,6 +306,7 @@ fn decide_internal( // Rule 4: consider a handover for a quality gain, which the dwell time // may refuse. + // 规则 4:为质量增益考虑切换,驻留时间可能拒绝它。 if best.satellite != current_satellite && best.quality > current_candidate.quality + policy.handover_margin { diff --git a/crates/network/apsis-handover/src/policy.rs b/crates/network/apsis-handover/src/policy.rs index 58d271b..a369839 100644 --- a/crates/network/apsis-handover/src/policy.rs +++ b/crates/network/apsis-handover/src/policy.rs @@ -9,6 +9,11 @@ use crate::error::{HandoverError, HandoverErrorKind}; /// current server must be abandoned, the margin decides when a better /// candidate is worth the switch, and the dwell time limits how often /// switches may happen at all. +/// +/// 切换策略的参数。 +/// +/// 三个参数协同工作。最低质量决定当前服务星何时必须被放弃,余量决定 +/// 更优候选何时值得切换,驻留时间则限制切换至多能多频繁发生。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct HandoverPolicy { /// The quality below which the current server is abandoned regardless of @@ -17,24 +22,40 @@ pub struct HandoverPolicy { /// Quality is a number the caller defines, and the policy is expressed /// in the same units. A value of 0.2 on a normalised scale means the /// server must stay above a fifth of full quality. + /// + /// 低于该质量时,无论还有什么可用,当前服务星都被放弃。 + /// + /// 质量是调用方定义的数,策略以相同单位表达。在归一化尺度上取 0.2 + /// 表示服务星必须保持在全质量的五分之一以上。 pub minimum_quality: f64, /// The amount by which a candidate must exceed the current server's /// quality before a handover is worthwhile. /// /// This is the hysteresis that suppresses oscillation between two /// satellites of similar quality. + /// + /// 候选超出当前服务星质量多少才值得切换的量。 + /// + /// 这是抑制质量相近的两颗卫星之间振荡的滞回量。 pub handover_margin: f64, /// The minimum time between handovers, in seconds. /// /// A handover is refused when the previous one was more recent than /// this, unless the current server has fallen below the minimum quality, /// in which case the link would fail anyway. + /// + /// 两次切换之间的最短时间,单位为秒。 + /// + /// 若上一次切换比这更近,则切换被拒绝,除非当前服务星已跌至最低 + /// 质量以下,此时链路无论如何都会失效。 pub minimum_dwell_seconds: f64, } impl Default for HandoverPolicy { /// Returns a policy with a minimum quality of 0.2, a margin of 0.1, and /// a dwell time of five seconds. + /// + /// 返回最低质量为 0.2、余量为 0.1、驻留时间为五秒的策略。 fn default() -> Self { Self { minimum_quality: 0.2, @@ -46,6 +67,8 @@ impl Default for HandoverPolicy { impl HandoverPolicy { /// Creates a policy from its three parameters. + /// + /// 由其三个参数创建策略。 pub const fn new( minimum_quality: f64, handover_margin: f64, @@ -65,6 +88,12 @@ impl HandoverPolicy { /// not be negative. The minimum quality may be anything finite, because /// a caller may place it outside the range of the quality scale to /// disable the forced handover. + /// + /// 当策略参数可用时返回 `true`。 + /// + /// 余量不得为负,因为负余量会使切换到更差候选也值得做;驻留时间也 + /// 不得为负。最低质量可以是任何有限值,因为调用方可以把它放在质量 + /// 尺度范围之外以禁用强制切换。 pub fn is_valid(&self) -> bool { self.minimum_quality.is_finite() && self.handover_margin.is_finite() @@ -74,6 +103,8 @@ impl HandoverPolicy { } /// Validates the policy, returning the reason when it is not usable. + /// + /// 校验策略,不可用时返回原因。 pub fn validate(&self) -> Result<(), HandoverError> { if !self.is_valid() { return Err( @@ -91,6 +122,10 @@ impl HandoverPolicy { /// The best candidate is the one with the greatest quality, with ties /// broken by the lower satellite identifier so that the result is /// deterministic. + /// + /// 返回该策略下的最优候选。 + /// + /// 最优候选是质量最大者,并列时以较小的卫星标识符打破,使结果确定。 pub fn best<'a>(&self, candidates: &'a [Candidate]) -> Option<&'a Candidate> { candidates.iter().max_by(|first, second| { first @@ -99,28 +134,43 @@ impl HandoverPolicy { .unwrap_or(core::cmp::Ordering::Equal) // Reversed, so that among equal qualities the lower // identifier wins in the maximum. + // 反转,使质量相等时较小的标识符在取最大中胜出。 .then_with(|| second.satellite.cmp(&first.satellite)) }) } } /// A record of one handover that has occurred. +/// +/// 一次已发生切换的记录。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct HandoverRecord { /// The satellite handed over from. + /// + /// 被切换离开的卫星。 pub from: usize, /// The satellite handed over to. + /// + /// 被切换至的卫星。 pub to: usize, /// The time of the handover, in seconds. + /// + /// 该切换的时间,单位为秒。 pub seconds: f64, /// The quality of the satellite handed over from. + /// + /// 被切换离开卫星的质量。 pub from_quality: f64, /// The quality of the satellite handed over to. + /// + /// 被切换至卫星的质量。 pub to_quality: f64, } impl HandoverRecord { /// Creates a record. + /// + /// 创建一条记录。 pub const fn new( from: usize, to: usize, @@ -138,6 +188,8 @@ impl HandoverRecord { } /// Returns the gain in quality that the handover achieved. + /// + /// 返回该切换所取得的质量增益。 pub fn quality_gain(&self) -> f64 { self.to_quality - self.from_quality } @@ -149,6 +201,11 @@ impl HandoverRecord { /// has to supply the candidates at each step. It is the form a simulation or /// a control loop uses, where the decision made at one step affects the one /// at the next through the dwell time. +/// +/// 跟踪服务卫星与切换历史。 +/// +/// 跟踪器拥有策略与决策历史,故调用方只需在每一步提供候选。这是仿真或 +/// 控制回路所用的形式——其中一步的决策经驻留时间影响下一步。 #[derive(Debug, Clone, PartialEq)] pub struct HandoverTracker { policy: HandoverPolicy, @@ -159,6 +216,8 @@ pub struct HandoverTracker { impl HandoverTracker { /// Creates a tracker with no serving satellite. + /// + /// 创建没有服务卫星的跟踪器。 pub fn new(policy: HandoverPolicy) -> Self { Self { policy, @@ -169,6 +228,8 @@ impl HandoverTracker { } /// Creates a tracker that is already serving a satellite. + /// + /// 创建已在服务某卫星的跟踪器。 pub fn serving(policy: HandoverPolicy, satellite: usize) -> Self { Self { policy, @@ -179,26 +240,36 @@ impl HandoverTracker { } /// Returns the policy. + /// + /// 返回策略。 pub fn policy(&self) -> HandoverPolicy { self.policy } /// Returns the satellite currently being served, if any. + /// + /// 返回当前正在服务的卫星(若有)。 pub fn current(&self) -> Option { self.serving } /// Returns the time of the last handover, if there has been one. + /// + /// 返回最后一次切换的时间(若曾发生)。 pub fn last_handover_seconds(&self) -> Option { self.last_handover_seconds } /// Returns the handover history, in time order. + /// + /// 按时间次序返回切换历史。 pub fn history(&self) -> &[HandoverRecord] { &self.history } /// Returns the number of handovers that have occurred. + /// + /// 返回已发生的切换次数。 pub fn handover_count(&self) -> usize { self.history.len() } @@ -257,6 +328,10 @@ impl HandoverTracker { /// /// Returns `None` when fewer than two handovers have occurred, because a /// single interval is not a mean. + /// + /// 返回两次切换之间的平均时间,单位为秒。 + /// + /// 发生的切换少于两次时返回 `None`,因为单个间隔算不上均值。 pub fn mean_interval_seconds(&self) -> Option { if self.history.len() < 2 { return None; @@ -270,6 +345,11 @@ impl HandoverTracker { /// The rate is measured across the whole history, from the first /// handover to the last. Returns `None` when there are fewer than two /// handovers or when they all occur at the same instant. + /// + /// 返回整个历史上单位时间的切换次数。 + /// + /// 速率跨越整个历史度量,从首次切换到末次。切换少于两次或它们都 + /// 发生在同一瞬时时返回 `None`。 pub fn handover_rate_per_second(&self) -> Option { let interval = self.mean_interval_seconds()?; if interval <= 0.0 { @@ -283,6 +363,11 @@ impl HandoverTracker { /// The value is the sum over the history, which can be negative when a /// handover was forced by a falling quality rather than chosen for a /// gain. + /// + /// 返回切换所获得的质量总和。 + /// + /// 该值是对整个历史求和,当切换是被下降的质量所迫而非为增益所选择 + /// 时可为负。 pub fn total_quality_gain(&self) -> f64 { self.history.iter().map(HandoverRecord::quality_gain).sum() } diff --git a/crates/network/apsis-topology/src/graph.rs b/crates/network/apsis-topology/src/graph.rs index 343e922..8f538bc 100644 --- a/crates/network/apsis-topology/src/graph.rs +++ b/crates/network/apsis-topology/src/graph.rs @@ -3,22 +3,35 @@ use crate::error::{TopologyError, TopologyErrorKind}; /// One link in a topology. +/// +/// 拓扑中的一条链路。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct Edge { /// The index of the first endpoint. + /// + /// 第一个端点的下标。 pub from: usize, /// The index of the second endpoint. + /// + /// 第二个端点的下标。 pub to: usize, /// The cost of traversing the link. /// /// The meaning is the caller's: a delay in seconds, a hop count, or any /// other measure a routing algorithm minimises. It must be finite and /// positive, so that adding edges never shortens a path. + /// + /// 穿越该链路的代价。 + /// + /// 其含义由调用方定义:以秒计的时延、跳数,或路由算法所最小化的 + /// 任何其他度量。它必须有限且为正,使添加边绝不缩短路径。 pub cost: f64, } impl Edge { /// Creates an edge. + /// + /// 创建一条边。 pub const fn new(from: usize, to: usize, cost: f64) -> Self { Self { from, to, cost } } @@ -27,6 +40,10 @@ impl Edge { /// /// A link between two satellites carries traffic both ways, so most /// topologies add both directions. + /// + /// 返回端点互换后的同一条边。 + /// + /// 两颗卫星之间的链路双向承载业务,故多数拓扑会添加两个方向。 pub const fn reversed(self) -> Self { Self { from: self.to, @@ -44,6 +61,13 @@ impl Edge { /// both directions. This is deliberate, because an inter-satellite link may /// have different capacity in each direction and a ground link is often /// asymmetric. +/// +/// 某一瞬时的节点与链路图。 +/// +/// 节点数在构造时固定,节点以其下标标识。边是有向的:从一节点向另一 +/// 节点添加边不会添加反向边,故需要双向链路的调用方要添加两个方向。 +/// 这是有意的,因为星间链路每个方向的容量可能不同,而地面链路往往 +/// 不对称。 #[derive(Debug, Clone, PartialEq, Default)] pub struct Topology { node_count: usize, @@ -57,6 +81,13 @@ pub struct Topology { /// satellites, into cubic work. The index holds positions rather than /// destinations so that `remove_edge`, which invalidates positions, can /// repair it in one pass. + /// + /// 各节点出发的边在 `edges` 中的位置。 + /// + /// 该索引使成员判断成为常数时间测试。没有它,询问某边是否已存在要 + /// 扫描整个边表,构建含 `E` 条边的图耗费 `O(E^2)`,这把星座的可见性 + /// 遍历(对卫星数二次)变成三次的工作量。该索引保存位置而非目的地, + /// 使会使位置失效的 `remove_edge` 能一遍修好它。 outgoing: Vec>, } @@ -65,6 +96,10 @@ impl Topology { /// /// This is not a `const fn` because it sizes the adjacency index, which /// allocates. + /// + /// 以给定节点数创建空拓扑。 + /// + /// 它不是 `const fn`,因为它要为邻接索引确定大小,而这会分配。 pub fn new(node_count: usize) -> Self { let mut outgoing = Vec::new(); outgoing.resize_with(node_count, Vec::new); @@ -76,21 +111,29 @@ impl Topology { } /// Returns the number of nodes. + /// + /// 返回节点的数量。 pub const fn node_count(&self) -> usize { self.node_count } /// Returns the number of edges. + /// + /// 返回边的数量。 pub fn edge_count(&self) -> usize { self.edges.len() } /// Returns `true` when there are no edges. + /// + /// 当没有边时返回 `true`。 pub fn is_empty(&self) -> bool { self.edges.is_empty() } /// Returns the edges. + /// + /// 返回各条边。 pub fn edges(&self) -> &[Edge] { &self.edges } @@ -101,6 +144,11 @@ impl Topology { /// cost is not finite and positive, or when the edge already exists. /// Rejecting a duplicate rather than replacing it keeps a topology free /// of silent contradictions about the cost of a link. + /// + /// 添加一条有向边。 + /// + /// 任一端点下标越界、代价非有限且为正,或该边已存在时返回错误。拒绝 + /// 重复边而非替换它,使拓扑不会对某条链路的代价产生无声的矛盾。 pub fn add_edge(&mut self, from: usize, to: usize, cost: f64) -> Result<(), TopologyError> { if from >= self.node_count || to >= self.node_count { return Err(TopologyError::new(TopologyErrorKind::NoSuchNode) @@ -124,6 +172,11 @@ impl Topology { /// If the second direction fails after the first succeeds, the first is /// rolled back, so the topology is left unchanged when the call reports /// an error. + /// + /// 以相同代价在两个方向添加边。 + /// + /// 若第一个方向成功后第二个方向失败,则回滚第一个,故调用报告错误 + /// 时拓扑保持不变。 pub fn add_bidirectional_edge( &mut self, first: usize, @@ -133,6 +186,7 @@ impl Topology { self.add_edge(first, second, cost)?; if let Err(error) = self.add_edge(second, first, cost) { // Remove the first direction so the topology is unchanged. + // 移除第一个方向,使拓扑保持不变。 self.remove_edge_unchecked(first, second); return Err(error); } @@ -140,6 +194,8 @@ impl Topology { } /// Returns `true` when an edge exists from one node to another. + /// + /// 当存在从一节点到另一节点的边时返回 `true`。 pub fn has_edge(&self, from: usize, to: usize) -> bool { self.outgoing.get(from).is_some_and(|positions| { positions @@ -151,6 +207,10 @@ impl Topology { /// Returns the cost of the edge between two nodes. /// /// Returns `None` when there is no such edge. + /// + /// 返回两节点之间边的代价。 + /// + /// 不存在该边时返回 `None`。 pub fn cost(&self, from: usize, to: usize) -> Option { let positions = self.outgoing.get(from)?; positions @@ -162,6 +222,10 @@ impl Topology { /// Removes the edge between two nodes. /// /// Returns an error when there is no such edge. + /// + /// 移除两节点之间的边。 + /// + /// 不存在该边时返回错误。 pub fn remove_edge(&mut self, from: usize, to: usize) -> Result<(), TopologyError> { if !self.has_edge(from, to) { return Err(TopologyError::new(TopologyErrorKind::NoSuchEdge) @@ -177,6 +241,11 @@ impl Topology { /// rebuilt from the surviving edges in one pass. Removal is rarer than /// insertion, so paying for a rebuild here is cheaper than keeping the /// index correct incrementally. + /// + /// 移除调用方已确认存在的边。 + /// + /// 移除边会重编号其后的每个位置,故索引由存活的边一遍重建。移除比 + /// 插入少见,故此处付出一次重建的代价比增量地保持索引正确更便宜。 fn remove_edge_unchecked(&mut self, from: usize, to: usize) { self.edges .retain(|edge| !(edge.from == from && edge.to == to)); @@ -184,6 +253,8 @@ impl Topology { } /// Rebuilds the adjacency index from the current edge list. + /// + /// 由当前边表重建邻接索引。 fn rebuild_index(&mut self) { for positions in &mut self.outgoing { positions.clear(); @@ -197,6 +268,10 @@ impl Topology { /// /// The result is in the order the edges were added, so it is /// deterministic for a given construction sequence. + /// + /// 返回自某节点一跳可达的节点下标。 + /// + /// 结果按边被添加的次序排列,故对给定的构建序列是确定的。 pub fn neighbours(&self, node: usize) -> Vec { self.outgoing .get(node) @@ -205,11 +280,15 @@ impl Topology { } /// Returns the number of edges leaving a node. + /// + /// 返回离开某节点的边数。 pub fn out_degree(&self, node: usize) -> usize { self.outgoing.get(node).map_or(0, Vec::len) } /// Returns the number of edges arriving at a node. + /// + /// 返回到达某节点的边数。 pub fn in_degree(&self, node: usize) -> usize { self.edges.iter().filter(|edge| edge.to == node).count() } @@ -218,6 +297,10 @@ impl Topology { /// /// The result has one entry per node, in node order, each listing the /// neighbours of that node. + /// + /// 返回每个节点的邻接。 + /// + /// 结果每个节点一项,按节点次序排列,各自列出该节点的邻居。 pub fn adjacency(&self) -> Vec> { (0..self.node_count) .map(|node| self.neighbours(node)) @@ -231,6 +314,11 @@ impl Topology { /// the first thing to check about a constellation snapshot: a /// constellation that has split into pieces can still carry traffic /// within each piece but not between them. + /// + /// 把边视为无向时返回连通分量的数量。 + /// + /// 它回答该拓扑构成多少个独立网络,这是检查星座快照时要看的第一件 + /// 事:已裂成数块的星座仍可在各块内承载业务,但块与块之间不行。 pub fn connected_components(&self) -> usize { if self.node_count == 0 { return 0; @@ -243,6 +331,7 @@ impl Topology { } components += 1; // A breadth first walk over the edges in both directions. + // 沿两个方向的边做广度优先遍历。 let mut stack = vec![start]; visited[start] = true; while let Some(node) = stack.pop() { @@ -266,6 +355,8 @@ impl Topology { /// Returns `true` when every node can reach every other node, treating /// the edges as undirected. + /// + /// 把边视为无向时,当每个节点都能到达其余每个节点,返回 `true`。 pub fn is_connected(&self) -> bool { self.node_count <= 1 || self.connected_components() == 1 } @@ -274,6 +365,10 @@ impl Topology { /// /// This is a crude measure of the network's extent, useful for comparing /// snapshots rather than for routing. + /// + /// 返回所有边的代价之和。 + /// + /// 这是对网络规模的一个粗略度量,用于比较快照而非用于路由。 pub fn total_cost(&self) -> f64 { self.edges.iter().map(|edge| edge.cost).sum() } diff --git a/crates/network/apsis-topology/src/series.rs b/crates/network/apsis-topology/src/series.rs index 31cda02..7f86c54 100644 --- a/crates/network/apsis-topology/src/series.rs +++ b/crates/network/apsis-topology/src/series.rs @@ -4,12 +4,18 @@ use crate::error::{TopologyError, TopologyErrorKind}; use crate::graph::Topology; /// One topology and the time from which it applies. +/// +/// 一个拓扑及其开始适用的时间。 #[derive(Debug, Clone, PartialEq)] pub struct TopologySample { /// The time from which this topology applies, in seconds from the /// caller's origin. + /// + /// 该拓扑开始适用的时间,以距调用方原点的秒数表示。 pub seconds: f64, /// The topology in force from that time. + /// + /// 自该时间起生效的拓扑。 pub topology: Topology, } @@ -32,6 +38,14 @@ impl TopologySample { /// /// The times must be strictly increasing. Two entries at the same time would /// make the topology at that instant ambiguous. +/// +/// 拓扑随时间变化的序列。 +/// +/// 该序列是阶跃函数。下标 `i` 处的项自其自身时间起适用,直到下标 +/// `i + 1` 处项的时间;最后一项适用于其后所有时间。查询首项之前的时间 +/// 返回第一个拓扑,故该序列的行为像是向前向后都延伸。 +/// +/// 各时间必须严格递增。同一时间的两项会使该瞬时的拓扑含糊。 #[derive(Debug, Clone, PartialEq, Default)] pub struct TopologySeries { samples: Vec, @@ -42,6 +56,10 @@ impl TopologySeries { /// /// Returns an error when the list is empty, when a time is not finite, /// or when the times are not strictly increasing. + /// + /// 由样本列表创建序列。 + /// + /// 列表为空、某时间非有限,或各时间非严格递增时返回错误。 pub fn new(samples: Vec) -> Result { if samples.is_empty() { return Err(TopologyError::new(TopologyErrorKind::EmptySeries)); @@ -71,6 +89,12 @@ impl TopologySeries { /// /// Returns an error when the lists differ in length or when the series /// itself is not valid. + /// + /// 由分别给出的时间列表与拓扑列表创建序列。 + /// + /// 两个列表必须长度相同。 + /// + /// 列表长度不同或序列自身无效时返回错误。 pub fn from_parts(times: Vec, topologies: Vec) -> Result { if times.len() != topologies.len() { return Err( @@ -90,11 +114,15 @@ impl TopologySeries { } /// Returns the samples. + /// + /// 返回各样本。 pub fn samples(&self) -> &[TopologySample] { &self.samples } /// Returns the number of samples. + /// + /// 返回样本的数量。 pub fn len(&self) -> usize { self.samples.len() } @@ -104,16 +132,25 @@ impl TopologySeries { /// A series built through either constructor is never empty, so this /// always returns `false`; it exists because a collection type should /// offer it alongside [`TopologySeries::len`]. + /// + /// 当没有样本时返回 `true`。 + /// + /// 经任一构造函数构建的序列都不会为空,故这总是返回 `false`;它的 + /// 存在是因为集合类型应在 [`TopologySeries::len`] 之外一并提供它。 pub fn is_empty(&self) -> bool { self.samples.is_empty() } /// Returns the first time in the series. + /// + /// 返回序列中的第一个时间。 pub fn start_seconds(&self) -> f64 { self.samples.first().map_or(0.0, |sample| sample.seconds) } /// Returns the last time in the series. + /// + /// 返回序列中的最后一个时间。 pub fn end_seconds(&self) -> f64 { self.samples.last().map_or(0.0, |sample| sample.seconds) } @@ -125,12 +162,20 @@ impl TopologySeries { /// last index. /// /// Returns `None` when the time is not finite or the series is empty. + /// + /// 返回某时间处适用的样本下标。 + /// + /// 该下标是时间不晚于查询的最后一个项,故序列之前的时间返回零, + /// 序列之后的时间返回最后的下标。 + /// + /// 时间非有限或序列为空时返回 `None`。 pub fn index_at(&self, seconds: f64) -> Option { if !seconds.is_finite() || self.samples.is_empty() { return None; } // The samples are ordered, so a binary search gives the insertion // point of the query, and the entry before it is the one in force. + // 样本已排序,故二分查找给出查询的插入点,其前一项即生效的那项。 match self .samples .binary_search_by(|sample| sample.seconds.partial_cmp(&seconds).expect("finite times")) @@ -144,6 +189,10 @@ impl TopologySeries { /// Returns the topology in force at a time. /// /// Returns `None` when the time is not finite or the series is empty. + /// + /// 返回某时间处生效的拓扑。 + /// + /// 时间非有限或序列为空时返回 `None`。 pub fn topology_at(&self, seconds: f64) -> Option<&Topology> { Some(&self.samples.get(self.index_at(seconds)?)?.topology) } @@ -154,6 +203,12 @@ impl TopologySeries { /// for the last entry. /// /// Returns `None` when the index is out of range. + /// + /// 返回某下标处样本适用的区间。 + /// + /// 上界是下一样本的时间,对最后一项为正无穷。 + /// + /// 下标越界时返回 `None`。 pub fn interval_of(&self, index: usize) -> Option<(f64, f64)> { let sample = self.samples.get(index)?; let end = self @@ -168,6 +223,11 @@ impl TopologySeries { /// This is the number of samples less one, since each change introduces /// a new entry. It measures how dynamic the network is: a series with /// many changes needs routing that accounts for them. + /// + /// 返回拓扑变化的次数。 + /// + /// 它是样本数减一,因为每次变化引入一个新项。它衡量网络的动态 + /// 程度:变化多的序列需要能考虑到这些变化的路由。 pub fn change_count(&self) -> usize { self.samples.len().saturating_sub(1) } @@ -178,6 +238,13 @@ impl TopologySeries { /// mean and the result covers the changes that have a measured interval. /// /// Returns `None` when there is only one sample. + /// + /// 返回变化之间的平均间隔,单位为秒。 + /// + /// 最后一项的持续时间无界,故它被排除在均值之外,结果涵盖有可测 + /// 间隔的那些变化。 + /// + /// 只有一个样本时返回 `None`。 pub fn mean_interval_seconds(&self) -> Option { if self.samples.len() < 2 { return None; @@ -193,18 +260,27 @@ impl TopologySeries { /// one link contributes two, since one link changed. /// /// Returns zero for a series of one sample. + /// + /// 返回整个序列中边变化的次数。 + /// + /// 每个拓扑与其前一个比较,统计出现与消失的边。相差一条链路的拓扑 + /// 贡献二,因为有一条链路发生了变化。 + /// + /// 单样本序列返回零。 pub fn edge_changes(&self) -> usize { let mut changes = 0; for pair in self.samples.windows(2) { let before = &pair[0].topology; let after = &pair[1].topology; // Edges present before but not after. + // 之前存在而之后不存在的边。 changes += before .edges() .iter() .filter(|edge| !after.has_edge(edge.from, edge.to)) .count(); // Edges present after but not before. + // 之后存在而之前不存在的边。 changes += after .edges() .iter() @@ -220,6 +296,11 @@ impl TopologySeries { /// value is taken from the first entry. A caller that changes the node /// count between entries is comparing two different networks and should /// build two series instead. + /// + /// 返回序列中的节点数。 + /// + /// 每个项都应描述同一组节点,故该值取自第一项。在各项之间改变节点 + /// 数的调用方是在比较两个不同的网络,应改为构建两个序列。 pub fn node_count(&self) -> usize { self.samples .first()