diff --git a/crates/sim/apsis-event/src/event.rs b/crates/sim/apsis-event/src/event.rs index 60c32ad..79afdfc 100644 --- a/crates/sim/apsis-event/src/event.rs +++ b/crates/sim/apsis-event/src/event.rs @@ -8,6 +8,11 @@ use core::fmt; /// in which it was scheduled. Two events that fall at the same instant are /// therefore distinguishable, which is what makes the delivery order /// repeatable. +/// +/// 已调度事件的标识符。 +/// +/// 该标识符把事件到期的时刻与其被调度的次序结合起来。因此落在同一 +/// 瞬时的两个事件可区分,这正是投递次序可重复的原因。 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct EventId { /// The scheduled time, in seconds from the start of the simulation. @@ -15,8 +20,15 @@ pub struct EventId { /// The value is stored as its bit pattern so that the derived ordering on /// the identifier matches the ordering on time for non-negative values, /// which is the only range a simulation uses. + /// + /// 调度时刻,以距仿真起点的秒数表示。 + /// + /// 该值以其位模式存储,使标识符上派生的次序对非负值与时间上的次序 + /// 一致;非负是仿真唯一使用的范围。 time_bits: u64, /// The order in which the event was scheduled. + /// + /// 该事件被调度的次序。 sequence: u64, } @@ -27,6 +39,11 @@ impl EventId { /// outside that range cannot use this type, which is deliberate: a /// simulation clock that goes backwards or becomes infinite has no /// meaningful order. + /// + /// 由时刻与序号创建标识符。 + /// + /// 该时刻必须有限且非负。时刻超出该范围的调用方无法使用本类型, + /// 这是有意为之:倒退或趋于无限的仿真时钟没有有意义的次序。 pub fn new(time: f64, sequence: u64) -> Option { if !time.is_finite() || time < 0.0 { return None; @@ -41,6 +58,11 @@ impl EventId { /// /// This is the crate internal constructor used by the queue, which has /// already checked the time before it stored the entry. + /// + /// 由一个已校验为可用的时刻创建标识符。 + /// + /// 这是队列所用的 crate 内部构造函数,它在存储该项之前已检查过 + /// 该时刻。 pub(crate) fn from_valid(time: f64, sequence: u64) -> Self { Self { time_bits: time.to_bits(), @@ -49,11 +71,15 @@ impl EventId { } /// Returns the scheduled time, in seconds. + /// + /// 返回调度时刻,单位为秒。 pub fn time(self) -> f64 { f64::from_bits(self.time_bits) } /// Returns the order in which the event was scheduled. + /// + /// 返回该事件被调度的次序。 pub const fn sequence(self) -> u64 { self.sequence } @@ -69,23 +95,38 @@ impl fmt::Display for EventId { /// /// The payload is the caller's: it names what should happen. The queue does /// not interpret it, so the same queue serves any model. +/// +/// 一个已调度的事件。 +/// +/// 载荷属于调用方:它指明应发生什么。队列不解释它,故同一队列可服务 +/// 任何模型。 #[derive(Debug, Clone, PartialEq)] pub struct Event { /// The identifier of this event. + /// + /// 本事件的标识符。 pub id: EventId, /// The scheduled time, in seconds from the start of the simulation. + /// + /// 调度时刻,以距仿真起点的秒数表示。 pub time: f64, /// The payload describing what should happen. + /// + /// 描述应发生什么的载荷。 pub payload: T, } impl Event { /// Returns `true` when the event is due at or before a time. + /// + /// 当该事件在某时刻或之前到期时返回 `true`。 pub fn is_due(&self, now: f64) -> bool { self.time <= now } /// Returns a copy of this event with a different payload. + /// + /// 返回本事件的一个具有不同载荷的副本。 pub fn with_payload(self, payload: U) -> Event { Event { id: self.id, @@ -98,6 +139,10 @@ impl Event { /// /// Returns `None` when the new time is not usable, which leaves the /// original event available for the caller to keep using. + /// + /// 返回本事件的一个调度在不同时刻的副本。 + /// + /// 新时刻不可用时返回 `None`,此时原事件仍可供调用方继续使用。 pub fn reschedule(&self, time: f64, sequence: u64) -> Option> where T: Clone, diff --git a/crates/sim/apsis-event/src/queue.rs b/crates/sim/apsis-event/src/queue.rs index dad9a5f..2af8fe5 100644 --- a/crates/sim/apsis-event/src/queue.rs +++ b/crates/sim/apsis-event/src/queue.rs @@ -10,6 +10,11 @@ use std::collections::BinaryHeap; /// reversed on time. Ties on time are broken by the sequence number, and /// that comparison is also reversed so that the earlier sequence is the /// greater element. +/// +/// 一个堆元素,其排序使最早的事件为最大者。 +/// +/// 标准库的堆是最大堆,故时间上的比较被反转。时间上的并列由序号打破, +/// 而该比较也被反转,使较早的序号成为较大的元素。 #[derive(Debug, Clone, Copy, PartialEq, Eq)] struct Scheduled { time_bits: u64, @@ -27,6 +32,8 @@ impl Ord for Scheduled { // Reverse the time so that the smallest time is the greatest heap // element. For equal times, the smaller sequence is the greater // element, so an earlier scheduled event is delivered first. + // 反转时间,使最小的时间成为最大的堆元素。时间相等时,较小的序号 + // 是较大的元素,故较早调度的事件先被投递。 other .time_bits .cmp(&self.time_bits) @@ -44,13 +51,24 @@ impl PartialOrd for Scheduled { /// /// The state is exposed so that a simulation loop can decide whether to /// advance the clock or to stop without popping an event first. +/// +/// 队列当前所持有的状态。 +/// +/// 该状态被公开,使仿真循环无需先弹出一个事件即可决定是推进时钟还是 +/// 停止。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct QueueState { /// The number of pending events. + /// + /// 待处理事件的数量。 pub pending: usize, /// The time of the earliest pending event, if there is one. + /// + /// 最早待处理事件的时间(若存在)。 pub next_time: Option, /// The number of events delivered so far. + /// + /// 迄今已投递的事件数。 pub delivered: u64, } @@ -69,6 +87,12 @@ impl QueueState { /// create two events with the same identifier. Sequence numbers start at /// zero and increase by one with every event scheduled, including events /// scheduled from inside the handling of another event. +/// +/// 按时间、再按调度次序排序的事件优先队列。 +/// +/// 序号由队列分配,故调用方无法意外创建两个具有相同标识符的事件。 +/// 序号从零开始,每个被调度的事件(包括在另一事件处理过程中调度的 +/// 事件)都使之加一。 #[derive(Debug, Clone)] pub struct EventQueue { pending: BinaryHeap, @@ -85,6 +109,8 @@ impl Default for EventQueue { impl EventQueue { /// Creates an empty queue. + /// + /// 创建一个空队列。 pub const fn new() -> Self { Self { pending: BinaryHeap::new(), @@ -95,6 +121,8 @@ impl EventQueue { } /// Returns the number of pending events. + /// + /// 返回待处理事件的数量。 pub fn len(&self) -> usize { self.pending.len() } @@ -107,11 +135,15 @@ impl EventQueue { } /// Returns the number of events delivered so far. + /// + /// 返回迄今已投递的事件数。 pub const fn delivered(&self) -> u64 { self.delivered } /// Returns what the queue currently holds. + /// + /// 返回队列当前所持有的状态。 pub fn state(&self) -> QueueState { QueueState { pending: self.pending.len(), @@ -123,6 +155,10 @@ impl EventQueue { /// Returns the time of the earliest pending event. /// /// Returns `None` when the queue is empty. + /// + /// 返回最早待处理事件的时间。 + /// + /// 队列为空时返回 `None`。 pub fn next_time(&self) -> Option { self.pending.peek().map(|entry| entry.time()) } @@ -135,6 +171,14 @@ impl EventQueue { /// Returns the identifier of the new event, or `None` when the time is /// not usable: a simulation clock runs forward from zero, so a negative /// or non-finite time is refused rather than stored. + /// + /// 在某个时刻调度一个事件。 + /// + /// 序号由队列分配,故同一瞬时各事件之间的投递次序即它们被调度的 + /// 次序。 + /// + /// 返回新事件的标识符;时刻不可用时返回 `None`:仿真时钟从零向前 + /// 运行,故负或非有限的时刻被拒绝而非存储。 pub fn schedule(&mut self, time: f64, payload: T) -> Option { let sequence = self.next_sequence; let id = EventId::new(time, sequence)?; @@ -146,6 +190,8 @@ impl EventQueue { // The payload slots are indexed by sequence, so gaps are filled with // `None` when a caller has popped an event without taking its // payload. + // 载荷槽位按序号索引,故当调用方弹出事件而未取其载荷时,空档以 + // `None` 填充。 let index = sequence as usize; if index < self.payloads.len() { self.payloads[index] = Some(payload); @@ -164,6 +210,12 @@ impl EventQueue { /// delay is relative to the current simulation time. /// /// Returns `None` when the resulting time is not usable. + /// + /// 在相对于某基准瞬时的时刻调度一个事件。 + /// + /// 这是处理器调度后续事件时所用的形式:延时相对于当前仿真时刻。 + /// + /// 所得时刻不可用时返回 `None`。 pub fn schedule_after(&mut self, now: f64, delay: f64, payload: T) -> Option { if !delay.is_finite() || delay < 0.0 { return None; @@ -176,6 +228,11 @@ impl EventQueue { /// Returns `None` when the queue is empty. The payload is taken from the /// queue, so the slot is left empty; scheduling a later event reuses the /// slot. + /// + /// 取出并返回最早的事件。 + /// + /// 队列为空时返回 `None`。载荷被取离队列,故该槽位被留空;调度 + /// 后续事件会复用该槽位。 pub fn pop(&mut self) -> Option> { let entry = self.pending.pop()?; let index = entry.sequence as usize; @@ -194,6 +251,8 @@ impl EventQueue { /// Returns a reference to the payload of the earliest event without /// removing it. + /// + /// 返回最早事件载荷的引用而不移除它。 pub fn peek(&self) -> Option<&T> { let entry = self.pending.peek()?; self.payloads @@ -205,6 +264,10 @@ impl EventQueue { /// /// The delivered count is left alone, because it records history rather /// than queue contents. + /// + /// 移除所有待处理事件并返回被丢弃的数量。 + /// + /// 已投递计数保持不变,因为它记录的是历史而非队列内容。 pub fn clear(&mut self) -> usize { let count = self.pending.len(); self.pending.clear(); @@ -220,6 +283,11 @@ impl EventQueue { /// The order is the order the events would have been delivered in, which /// is what a caller wants when it records the remaining schedule at the /// end of a run. + /// + /// 按投递次序返回所有待处理事件,并使队列为空。 + /// + /// 该次序即各事件本会被投递的次序,这正是调用方在运行结束时记录 + /// 剩余调度所需的。 pub fn drain(&mut self) -> Vec> { let mut events = Vec::with_capacity(self.pending.len()); while let Some(event) = self.pop() { diff --git a/crates/sim/apsis-output/src/record.rs b/crates/sim/apsis-output/src/record.rs index 2745f09..6398e41 100644 --- a/crates/sim/apsis-output/src/record.rs +++ b/crates/sim/apsis-output/src/record.rs @@ -7,19 +7,32 @@ use core::fmt; /// The three kinds separate the things a caller usually wants to tell apart /// when it reads a trace back: a value the model measured, a decision it /// made, and a problem it wants to surface. +/// +/// 一条记录所关于的内容。 +/// +/// 三种类别区分了调用方回读轨迹时通常想分辨的事物:模型测得的值、模型 +/// 作出的决策,以及它想揭示的问题。 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum RecordKind { /// A value the model measured, such as a range or a rate. + /// + /// 模型测得的值,如距离或速率。 Observation, /// A change the model decided on, such as entering eclipse. + /// + /// 模型决定的变更,如进入地影。 State, /// A condition worth surfacing, such as a value outside its bounds. + /// + /// 值得揭示的情形,如某值超出其范围。 Warning, } impl RecordKind { /// Returns a short name for the kind. + /// + /// 返回该类别的简称。 pub const fn name(self) -> &'static str { match self { Self::Observation => "observation", @@ -29,6 +42,8 @@ impl RecordKind { } /// Returns `true` when the kind is a warning. + /// + /// 当该类别为警告时返回 `true`。 pub const fn is_warning(self) -> bool { matches!(self, Self::Warning) } @@ -44,20 +59,34 @@ impl fmt::Display for RecordKind { /// /// A record is small and copied by value, so a trace holds a flat list rather /// than a tree of references. +/// +/// 一个被记录的值。 +/// +/// 记录很小且按值复制,故轨迹保存的是扁平列表而非引用树。 #[derive(Debug, Clone, PartialEq)] pub struct Record { /// The simulation time the record was made at, in seconds. + /// + /// 作出该记录时的仿真时刻,单位为秒。 pub time: f64, /// What the record is about. + /// + /// 该记录所关于的内容。 pub kind: RecordKind, /// What the value is about. + /// + /// 该值所关于的内容。 pub label: String, /// The value recorded. + /// + /// 所记录的值。 pub value: f64, } impl Record { /// Creates a record. + /// + /// 创建一条记录。 pub fn new(time: f64, kind: RecordKind, label: impl Into, value: f64) -> Self { Self { time, @@ -68,16 +97,22 @@ impl Record { } /// Creates an observation record. + /// + /// 创建一条观测记录。 pub fn observation(time: f64, label: impl Into, value: f64) -> Self { Self::new(time, RecordKind::Observation, label, value) } /// Creates a state record. + /// + /// 创建一条状态记录。 pub fn state(time: f64, label: impl Into, value: f64) -> Self { Self::new(time, RecordKind::State, label, value) } /// Creates a warning record. + /// + /// 创建一条警告记录。 pub fn warning(time: f64, label: impl Into, value: f64) -> Self { Self::new(time, RecordKind::Warning, label, value) } @@ -86,6 +121,10 @@ impl Record { /// /// A record whose time or value is not finite has no place in a trace, /// because it cannot be ordered or summarized. + /// + /// 当该记录可用时返回 `true`。 + /// + /// 时间或值非有限的记录在轨迹中没有位置,因为它无法被排序或汇总。 pub fn is_valid(&self) -> bool { self.time.is_finite() && self.value.is_finite() && !self.label.is_empty() } diff --git a/crates/sim/apsis-output/src/recorder.rs b/crates/sim/apsis-output/src/recorder.rs index b8ad37e..07dd76b 100644 --- a/crates/sim/apsis-output/src/recorder.rs +++ b/crates/sim/apsis-output/src/recorder.rs @@ -21,6 +21,24 @@ use crate::trace::Trace; /// assert_eq!(summary.count, 2); /// assert_eq!(summary.change, -50.0); /// ``` +/// +/// 在仿真运行过程中收集记录。 +/// +/// 记录器是轨迹的写入侧:处理器为其想保留的每个值调用它的某个方法, +/// 并在运行结束时索取轨迹。二者分开,使处理器可以持有记录器而轨迹 +/// 在别处被读取。 +/// +/// ``` +/// use apsis_output::Recorder; +/// +/// let mut recorder = Recorder::new(); +/// recorder.observe(0.0, "range", 1_000.0); +/// recorder.observe(10.0, "range", 950.0); +/// +/// let summary = recorder.trace().summarize("range").expect("the series exists"); +/// assert_eq!(summary.count, 2); +/// assert_eq!(summary.change, -50.0); +/// ``` #[derive(Debug, Clone, Default)] pub struct Recorder { trace: Trace, @@ -29,6 +47,8 @@ pub struct Recorder { impl Recorder { /// Creates an empty recorder. + /// + /// 创建一个空记录器。 pub const fn new() -> Self { Self { trace: Trace::new(), @@ -40,6 +60,10 @@ impl Recorder { /// /// Returns `false` when the record is not usable, which a caller can /// ignore when the value is known to be finite. + /// + /// 记录给定类别的一个值。 + /// + /// 该记录不可用时返回 `false`;当值已知为有限时调用方可忽略。 pub fn record( &mut self, time: f64, @@ -55,26 +79,36 @@ impl Recorder { } /// Records an observation. + /// + /// 记录一次观测。 pub fn observe(&mut self, time: f64, label: impl Into, value: f64) -> bool { self.record(time, RecordKind::Observation, label, value) } /// Records a state change. + /// + /// 记录一次状态变更。 pub fn note(&mut self, time: f64, label: impl Into, value: f64) -> bool { self.record(time, RecordKind::State, label, value) } /// Records a warning. + /// + /// 记录一次警告。 pub fn warn(&mut self, time: f64, label: impl Into, value: f64) -> bool { self.record(time, RecordKind::Warning, label, value) } /// Returns the number of records made. + /// + /// 返回已作出的记录数。 pub fn len(&self) -> usize { self.trace.len() } /// Returns `true` when no records have been made. + /// + /// 当尚无记录时返回 `true`。 pub fn is_empty(&self) -> bool { self.trace.is_empty() } @@ -83,11 +117,18 @@ impl Recorder { /// /// A caller that records values from a model checks this to see whether /// any value was dropped, rather than comparing counts by hand. + /// + /// 返回因不可用而被拒绝的记录数。 + /// + /// 从模型记录值的调用方检查它即可看出是否有值被丢弃,而无需手工 + /// 比较计数。 pub const fn refused(&self) -> u64 { self.refused } /// Returns the trace recorded so far. + /// + /// 返回迄今所记录的轨迹。 pub const fn trace(&self) -> &Trace { &self.trace } @@ -97,11 +138,19 @@ impl Recorder { /// This is what a caller does between runs: the finished trace goes to /// whoever reads it, and the recorder starts fresh. The refused count is /// not reset, because it records the whole life of the recorder. + /// + /// 返回轨迹并留下一个空轨迹。 + /// + /// 这是调用方在两次运行之间所做的:已完成的轨迹交给读取它的一方, + /// 记录器重新开始。被拒绝的计数不重置,因为它记录记录器的整个 + /// 生命期。 pub fn take_trace(&mut self) -> Trace { core::mem::take(&mut self.trace) } /// Removes every record without changing the refused count. + /// + /// 移除所有记录而不改变被拒绝的计数。 pub fn clear(&mut self) -> usize { self.trace.clear() } diff --git a/crates/sim/apsis-output/src/trace.rs b/crates/sim/apsis-output/src/trace.rs index 47b67d3..b61241a 100644 --- a/crates/sim/apsis-output/src/trace.rs +++ b/crates/sim/apsis-output/src/trace.rs @@ -9,6 +9,11 @@ use std::collections::BTreeMap; /// the order a model recorded in is part of what it decided. A caller that /// needs time order asks for [`Trace::in_time_order`], which leaves the /// original alone. +/// +/// 按作出次序排列的记录列表。 +/// +/// 轨迹保留插入次序而不按时间排序,因为模型记录的次序是其决策的一部分。 +/// 需要时间次序的调用方索取 [`Trace::in_time_order`],它不改变原轨迹。 #[derive(Debug, Clone, Default, PartialEq)] pub struct Trace { records: Vec, @@ -16,6 +21,8 @@ pub struct Trace { impl Trace { /// Creates an empty trace. + /// + /// 创建一个空轨迹。 pub const fn new() -> Self { Self { records: Vec::new(), @@ -23,21 +30,29 @@ impl Trace { } /// Creates a trace from a list of records. + /// + /// 由记录列表创建轨迹。 pub fn from_records(records: Vec) -> Self { Self { records } } /// Returns the number of records. + /// + /// 返回记录的数量。 pub fn len(&self) -> usize { self.records.len() } /// Returns `true` when there are no records. + /// + /// 当没有记录时返回 `true`。 pub fn is_empty(&self) -> bool { self.records.is_empty() } /// Returns the records in the order they were made. + /// + /// 按作出次序返回各记录。 pub fn records(&self) -> &[Record] { &self.records } @@ -46,6 +61,11 @@ impl Trace { /// /// A record whose time or value is not finite is refused rather than /// stored, so a trace never holds a value that cannot be summarized. + /// + /// 添加一条记录,不可用时返回 `false`。 + /// + /// 时间或值非有限的记录被拒绝而非存储,故轨迹绝不保存无法汇总的 + /// 值。 pub fn push(&mut self, record: Record) -> bool { if !record.is_valid() { return false; @@ -55,6 +75,8 @@ impl Trace { } /// Returns `true` when no record has a time or value that is not finite. + /// + /// 当没有任何记录的时间或值非有限时返回 `true`。 pub fn is_stable(&self) -> bool { self.records.iter().all(Record::is_valid) } @@ -63,6 +85,11 @@ impl Trace { /// /// Records at the same instant keep their insertion order, so the sort is /// stable and the result does not depend on the sort algorithm. + /// + /// 按时间次序返回各记录,最早在前。 + /// + /// 同一瞬时的记录保持其插入次序,故该排序是稳定的,结果不依赖于 + /// 排序算法。 pub fn in_time_order(&self) -> Vec { let mut ordered = self.records.clone(); ordered.sort_by(|left, right| { @@ -74,6 +101,8 @@ impl Trace { } /// Returns the records of one kind, in order. + /// + /// 按次序返回某一类别的记录。 pub fn of_kind(&self, kind: RecordKind) -> Vec<&Record> { self.records .iter() @@ -82,6 +111,8 @@ impl Trace { } /// Returns the records with a label, in order. + /// + /// 按次序返回具有某标签的记录。 pub fn with_label(&self, label: &str) -> Vec<&Record> { self.records .iter() @@ -93,6 +124,11 @@ impl Trace { /// /// This is what a caller asks for after a run: the final value a series /// reached. Returns `None` when no record has the label. + /// + /// 返回具有某标签的最后一条记录的值。 + /// + /// 这是调用方在运行结束后所索取的:某序列达到的最终值。没有记录 + /// 具有该标签时返回 `None`。 pub fn value_of(&self, label: &str) -> Option { self.records .iter() @@ -102,6 +138,8 @@ impl Trace { } /// Returns the time of the last record with a label. + /// + /// 返回具有某标签的最后一条记录的时间。 pub fn last_time_of(&self, label: &str) -> Option { self.records .iter() @@ -114,6 +152,11 @@ impl Trace { /// /// The order is sorted rather than first seen, so that two traces with /// the same labels list them the same way. + /// + /// 按排序次序返回出现的标签。 + /// + /// 次序是按排序而非按首次出现,使具有相同标签的两个轨迹以相同 + /// 方式列出它们。 pub fn labels(&self) -> Vec<&str> { let mut labels: Vec<&str> = self .records @@ -128,6 +171,10 @@ impl Trace { /// Returns a summary of the series recorded under a label. /// /// Returns `None` when no record has the label. + /// + /// 返回在某标签下所记录序列的汇总。 + /// + /// 没有记录具有该标签时返回 `None`。 pub fn summarize(&self, label: &str) -> Option { let values: Vec = self .records @@ -157,6 +204,8 @@ impl Trace { } /// Returns the number of warnings in the trace. + /// + /// 返回轨迹中警告的数量。 pub fn warning_count(&self) -> usize { self.records .iter() @@ -165,6 +214,8 @@ impl Trace { } /// Returns a summary of every label, in sorted label order. + /// + /// 按标签排序次序返回每个标签的汇总。 pub fn summaries(&self) -> Vec { self.labels() .into_iter() @@ -176,6 +227,10 @@ impl Trace { /// /// This is the length of the longest series, which a caller uses to size /// a table of the trace. + /// + /// 返回共享同一标签的记录数的最大值。 + /// + /// 它是最长序列的长度,调用方据此确定轨迹表格的大小。 pub fn longest_series(&self) -> usize { let mut counts: BTreeMap<&str, usize> = BTreeMap::new(); for record in &self.records { @@ -185,6 +240,8 @@ impl Trace { } /// Removes every record and returns how many were discarded. + /// + /// 移除所有记录并返回被丢弃的数量。 pub fn clear(&mut self) -> usize { let count = self.records.len(); self.records.clear(); @@ -193,33 +250,55 @@ impl Trace { } /// A summary of one series of records. +/// +/// 某一条记录序列的汇总。 #[derive(Debug, Clone, PartialEq)] pub struct SeriesSummary { /// The label the series was recorded under. + /// + /// 该序列所记录用的标签。 pub label: String, /// The number of records in the series. + /// + /// 该序列中记录的数量。 pub count: usize, /// The smallest value recorded. + /// + /// 所记录的最小值。 pub min: f64, /// The greatest value recorded. + /// + /// 所记录的最大值。 pub max: f64, /// The mean of the values. + /// + /// 各值的均值。 pub mean: f64, /// The first value recorded. + /// + /// 所记录的第一个值。 pub first: f64, /// The last value recorded. + /// + /// 所记录的最后一个值。 pub last: f64, /// The difference between the last and first values. + /// + /// 最后一个值与第一个值之差。 pub change: f64, } impl SeriesSummary { /// Returns the spread between the greatest and smallest values. + /// + /// 返回最大值与最小值之间的跨度。 pub fn range(&self) -> f64 { self.max - self.min } /// Returns `true` when the series never changed. + /// + /// 当该序列从未变化时返回 `true`。 pub fn is_constant(&self) -> bool { self.max == self.min } diff --git a/crates/sim/apsis-scenario/src/error.rs b/crates/sim/apsis-scenario/src/error.rs index d58c69d..64f743a 100644 --- a/crates/sim/apsis-scenario/src/error.rs +++ b/crates/sim/apsis-scenario/src/error.rs @@ -9,35 +9,65 @@ use core::fmt; #[non_exhaustive] pub enum ScenarioErrorKind { /// The scenario name is empty. + /// + /// 场景名称为空。 EmptyName, /// A resource with the identifier already exists. + /// + /// 已存在具有该标识符的资源。 DuplicateResource, /// A task with the identifier already exists. + /// + /// 已存在具有该标识符的任务。 DuplicateTask, /// A requirement names a resource that is not in the scenario. + /// + /// 某需求指名的资源不在场景中。 UnknownResource, /// A precedence constraint names a task that is not in the scenario. + /// + /// 某先后约束指名的任务不在场景中。 UnknownTask, /// A resource in the scenario is itself not usable. + /// + /// 场景中的某资源自身不可用。 InvalidResource, /// A task in the scenario is itself not usable. + /// + /// 场景中的某任务自身不可用。 InvalidTask, /// A precedence constraint would close a cycle. + /// + /// 某先后约束将形成环。 CyclicPrecedence, /// A time limit is negative or not finite. + /// + /// 某时限为负或非有限。 InvalidTimeLimit, /// An epoch is negative or not finite. + /// + /// 某历元为负或非有限。 InvalidEpoch, /// A line has too few arguments for its keyword. + /// + /// 某行的参数对其关键字而言太少。 MissingArgument, /// A line has an argument that cannot be read as the expected type. + /// + /// 某行的某参数无法按预期类型读取。 BadArgument, /// A line does not begin with a known keyword. + /// + /// 某行未以已知关键字开头。 UnknownKeyword, /// A keyword appears before the scenario header, or a required header is /// missing. + /// + /// 某关键字出现在场景头部之前,或缺少必需的头部。 MissingHeader, /// A field appears more than once where only one is allowed. + /// + /// 某字段在只允许出现一次处出现了多次。 DuplicateField, } @@ -65,6 +95,8 @@ impl fmt::Display for ScenarioErrorKind { } /// A failure while building, loading, or validating a scenario. +/// +/// 构建、加载或校验场景时发生的失败。 #[derive(Debug, Clone, PartialEq, Eq)] pub struct ScenarioError { kind: ScenarioErrorKind, @@ -93,6 +125,8 @@ impl ScenarioError { } /// Attaches the one based line number a load failure came from. + /// + /// 附加加载失败所来自的、以 1 为基的行号。 pub fn with_line(mut self, line: usize) -> Self { self.line = Some(line); self @@ -113,6 +147,8 @@ impl ScenarioError { } /// Returns the line number this failure came from, if any. + /// + /// 返回该失败所来自的行号(若有)。 pub fn line(&self) -> Option { self.line } diff --git a/crates/sim/apsis-scenario/src/parse.rs b/crates/sim/apsis-scenario/src/parse.rs index 023942f..6e5af7b 100644 --- a/crates/sim/apsis-scenario/src/parse.rs +++ b/crates/sim/apsis-scenario/src/parse.rs @@ -17,6 +17,17 @@ use apsis_task::{Task, TaskId}; /// /// Every failure carries the line it came from, so a reader can be pointed at /// the text that caused it. +/// +/// 由其文本形式读取场景。 +/// +/// 该格式在 crate 文档中描述。解析分三遍进行,使 `require` 行可以指名 +/// 文件中稍后才添加的资源,`precedence` 行可以任意次序指名任务: +/// +/// 1. 头部与资源, +/// 2. 任务及其需求, +/// 3. 先后约束。 +/// +/// 每次失败都携带其所来自的行,使读者可被指向造成它的文本。 pub(crate) fn parse(text: &str) -> Result { let lines = collect_lines(text); let mut scenario = parse_resources(&lines)?; @@ -28,6 +39,8 @@ pub(crate) fn parse(text: &str) -> Result { /// One significant line: its one based number and its whitespace separated /// fields. +/// +/// 一个有意义的行:其以 1 为基的行号及其以空白分隔的字段。 struct Line<'a> { number: usize, fields: Vec<&'a str>, @@ -41,11 +54,17 @@ impl Line<'_> { /// Returns the fields from an index to the end, joined by single spaces. /// /// This is used for the scenario name, which may contain spaces. + /// + /// 返回自某下标到末尾的字段,以单个空格连接。 + /// + /// 用于场景名称,它可能含有空格。 fn rest(&self, index: usize) -> String { self.fields[index.min(self.fields.len())..].join(" ") } /// Returns the field at an index, or a missing argument error. + /// + /// 返回某下标处的字段,或一个缺少参数的错误。 fn field(&self, index: usize) -> Result<&str, ScenarioError> { self.fields.get(index).copied().ok_or_else(|| { ScenarioError::new(ScenarioErrorKind::MissingArgument) @@ -93,12 +112,16 @@ impl Line<'_> { } /// Reports an error at this line. + /// + /// 在本行报告一个错误。 fn fail(&self, kind: ScenarioErrorKind) -> ScenarioError { ScenarioError::new(kind).with_line(self.number) } } /// Splits text into significant lines, dropping blanks and comments. +/// +/// 把文本拆分为有意义的行,丢弃空行与注释。 fn collect_lines(text: &str) -> Vec> { text.lines() .enumerate() @@ -120,11 +143,15 @@ fn collect_lines(text: &str) -> Vec> { } /// The header keywords, which a resource or task pass must not see. +/// +/// 头部关键字,资源或任务那一遍不得看到它们。 fn is_header(keyword: &str) -> bool { matches!(keyword, "scenario" | "epoch" | "time_limit" | "event_limit") } /// The resource pass. +/// +/// 资源那一遍。 fn parse_resources(lines: &[Line<'_>]) -> Result { let mut name: Option = None; let mut scenario: Option = None; @@ -136,6 +163,7 @@ fn parse_resources(lines: &[Line<'_>]) -> Result { let keyword = line.keyword(); // A resource must follow the header, because the scenario is built // when the name is read. + // 资源必须跟在头部之后,因为场景是在读取名称时构建的。 if keyword == "scenario" { if name.is_some() { return Err(line.fail(ScenarioErrorKind::DuplicateField)); @@ -217,6 +245,7 @@ fn parse_resources(lines: &[Line<'_>]) -> Result { return Err(line.fail(ScenarioErrorKind::MissingHeader)); } // The task and precedence keywords are read in a later pass. + // 任务与先后约束关键字在稍后那一遍中读取。 "task" | "task_window" | "task_priority" | "task_mandatory" | "require" | "precedence" => {} _ => return Err(line.fail(ScenarioErrorKind::UnknownKeyword)), @@ -227,6 +256,8 @@ fn parse_resources(lines: &[Line<'_>]) -> Result { } /// The task pass. +/// +/// 任务那一遍。 fn parse_tasks(lines: &[Line<'_>], scenario: &mut Scenario) -> Result<(), ScenarioError> { let mut current: Option = None; @@ -275,6 +306,8 @@ fn parse_tasks(lines: &[Line<'_>], scenario: &mut Scenario) -> Result<(), Scenar } /// Adds the task being built, if there is one. +/// +/// 添加正在构建的任务(若有)。 fn flush(current: &mut Option, scenario: &mut Scenario) -> Result<(), ScenarioError> { if let Some(task) = current.take() { scenario.add_task(task)?; @@ -283,6 +316,8 @@ fn flush(current: &mut Option, scenario: &mut Scenario) -> Result<(), Scen } /// Returns the task a qualifier line applies to, or an error naming the line. +/// +/// 返回某限定行所适用的任务,或一个指明该行的错误。 fn require_current<'a>( current: &'a Option, line: &Line<'_>, @@ -295,6 +330,8 @@ fn require_current<'a>( } /// The precedence pass. +/// +/// 先后约束那一遍。 fn parse_precedence(lines: &[Line<'_>], scenario: &mut Scenario) -> Result<(), ScenarioError> { for line in lines { if line.keyword() != "precedence" { diff --git a/crates/sim/apsis-scenario/src/scenario.rs b/crates/sim/apsis-scenario/src/scenario.rs index 352c364..71828cb 100644 --- a/crates/sim/apsis-scenario/src/scenario.rs +++ b/crates/sim/apsis-scenario/src/scenario.rs @@ -12,6 +12,12 @@ use core::fmt; /// respect. It is built either with the builder methods or by parsing text, /// and both routes apply the same checks, so a scenario that parses is one /// that would have been accepted by the builders. +/// +/// 一次仿真的初始条件。 +/// +/// 场景保存计划可用的资源、必须放置的任务、它们之间的约束,以及一次 +/// 运行应遵守的限制。它既可用构建器方法构建,也可由文本解析,两条 +/// 路径施加相同的检查,故能解析的场景即是构建器会接受的场景。 #[derive(Debug, Clone, PartialEq)] pub struct Scenario { name: String, @@ -25,6 +31,8 @@ pub struct Scenario { impl Scenario { /// Creates an empty scenario with an epoch at zero. + /// + /// 创建历元为零的空场景。 pub fn new(name: impl Into) -> Self { Self { name: name.into(), @@ -38,27 +46,37 @@ impl Scenario { } /// Returns the name of the scenario. + /// + /// 返回该场景的名称。 pub fn name(&self) -> &str { &self.name } /// Returns the time the simulation clock starts at, in seconds. + /// + /// 返回仿真时钟的起始时刻,单位为秒。 pub fn epoch(&self) -> f64 { self.epoch } /// Returns the latest time the run will process, if one is set. + /// + /// 返回该运行将处理的最晚时刻(若已设置)。 pub fn time_limit(&self) -> Option { self.time_limit } /// Returns the greatest number of events the run will execute, if one is /// set. + /// + /// 返回该运行将执行的事件数上限(若已设置)。 pub const fn event_limit(&self) -> Option { self.event_limit } /// Returns the resources in the order they were added. + /// + /// 按添加次序返回各资源。 pub fn resources(&self) -> &[Resource] { &self.resources } @@ -67,6 +85,11 @@ impl Scenario { /// /// A caller that changes a resource is responsible for keeping the /// scenario consistent; [`Scenario::validate`] checks it afterwards. + /// + /// 返回各资源以供修改。 + /// + /// 修改资源的调用方负责保持场景一致;[`Scenario::validate`] 随后 + /// 检查它。 pub fn resources_mut(&mut self) -> &mut Vec { &mut self.resources } @@ -75,16 +98,25 @@ impl Scenario { /// /// The format is described on the crate documentation. A scenario written /// with [`Scenario::to_text`] parses back to an equal scenario. + /// + /// 由其文本形式读取场景。 + /// + /// 该格式在 crate 文档中描述。用 [`Scenario::to_text`] 写出的场景 + /// 解析回相等的场景。 pub fn parse(text: &str) -> Result { crate::parse::parse(text) } /// Returns the tasks in the order they were added. + /// + /// 按添加次序返回各任务。 pub fn tasks(&self) -> &[Task] { &self.tasks } /// Returns the precedence constraints. + /// + /// 返回先后约束。 pub const fn precedence(&self) -> &PrecedenceGraph { &self.precedence } @@ -93,6 +125,11 @@ impl Scenario { /// /// Returns the scenario unchanged in value but not in type; the caller /// uses the returned value. The epoch must be finite and non-negative. + /// + /// 设置仿真时钟的起始时刻。 + /// + /// 返回的场景值不变而类型改变;调用方使用返回的值。历元必须有限 + /// 且非负。 pub fn with_epoch(mut self, epoch: f64) -> Self { self.epoch = epoch; self @@ -102,6 +139,11 @@ impl Scenario { /// /// A negative or non-finite limit clears the limit rather than storing /// it, matching the engine, which ignores a limit it cannot use. + /// + /// 设置该运行将处理的最晚时刻。 + /// + /// 负或非有限的限制清除该限制而非存储它,与引擎一致,后者忽略 + /// 它无法使用的限制。 pub const fn with_time_limit(mut self, limit: f64) -> Self { self.time_limit = if limit.is_finite() && limit >= 0.0 { Some(limit) @@ -115,12 +157,18 @@ impl Scenario { /// /// A limit of zero clears the limit rather than storing it, matching the /// engine, which treats zero as no limit. + /// + /// 设置该运行将执行的事件数上限。 + /// + /// 限制为零清除该限制而非存储它,与引擎一致,后者把零视为无限制。 pub const fn with_event_limit(mut self, limit: u64) -> Self { self.event_limit = if limit == 0 { None } else { Some(limit) }; self } /// Sets the epoch, reporting a value the clock cannot use. + /// + /// 设置历元,并报告时钟无法使用的值。 pub fn set_epoch(&mut self, epoch: f64) -> Result<(), ScenarioError> { if !epoch.is_finite() || epoch < 0.0 { return Err(ScenarioError::new(ScenarioErrorKind::InvalidEpoch) @@ -131,6 +179,8 @@ impl Scenario { } /// Sets the time limit, reporting a value a run cannot use. + /// + /// 设置时限,并报告运行无法使用的值。 pub fn set_time_limit(&mut self, limit: f64) -> Result<(), ScenarioError> { if !limit.is_finite() || limit < 0.0 { return Err(ScenarioError::new(ScenarioErrorKind::InvalidTimeLimit) @@ -144,6 +194,10 @@ impl Scenario { /// /// The resource itself is also checked, so a scenario cannot hold a /// resource that a scheduler would refuse. + /// + /// 添加一个资源,并报告标识符已被使用的那种情况。 + /// + /// 资源自身也受检查,故场景无法保存调度器会拒绝的资源。 pub fn add_resource(&mut self, resource: Resource) -> Result<(), ScenarioError> { if !resource.has_valid_capacity() { return Err( @@ -170,6 +224,10 @@ impl Scenario { /// /// The task's requirements must name resources the scenario holds, and /// the task itself must be usable. + /// + /// 添加一个任务,并报告标识符已被使用的那种情况。 + /// + /// 任务的需求必须指名场景所持有的资源,且任务自身必须可用。 pub fn add_task(&mut self, task: Task) -> Result<(), ScenarioError> { if !task.is_valid() { return Err( @@ -195,6 +253,8 @@ impl Scenario { } /// Adds a precedence constraint, reporting a cycle or a missing task. + /// + /// 添加一条先后约束,并报告成环或缺失任务的情况。 pub fn add_precedence(&mut self, before: TaskId, after: TaskId) -> Result<(), ScenarioError> { for id in [before, after] { if !self.tasks.iter().any(|task| task.id == id) { @@ -209,6 +269,8 @@ impl Scenario { } /// Returns the resources as a set, for a scheduler. + /// + /// 返回资源集合,供调度器使用。 pub fn resource_set(&self) -> Result { ResourceSet::from_resources(self.resources.clone()).map_err(|_| { ScenarioError::new(ScenarioErrorKind::DuplicateResource).with_detail("resources") @@ -220,6 +282,11 @@ impl Scenario { /// A scenario built with the builder methods is already checked as it is /// built; this is for one that was assembled field by field or read from /// text, and it reports the first problem found. + /// + /// 一次性检查整个场景。 + /// + /// 用构建器方法构建的场景在构建时已被检查;本方法用于逐字段拼装或 + /// 从文本读取的场景,它报告所发现的第一个问题。 pub fn validate(&self) -> Result<(), ScenarioError> { if self.name.trim().is_empty() { return Err(ScenarioError::new(ScenarioErrorKind::EmptyName)); @@ -273,6 +340,8 @@ impl Scenario { // A precedence graph cannot hold a cycle once built, because it // refuses one as it is added, but the tasks it names may still be // missing from the scenario. + // 先后关系图一旦构建便不能含环,因为它在添加时即拒绝环,但它所 + // 指名的任务仍可能缺失于场景。 for task in &self.tasks { for other in self.precedence.successors_of(task.id) { if !self.tasks.iter().any(|candidate| candidate.id == other) { @@ -285,6 +354,8 @@ impl Scenario { } /// Returns a summary of what the scenario holds. + /// + /// 返回场景所持有内容的摘要。 pub fn summary(&self) -> ScenarioSummary { ScenarioSummary { resources: self.resources.len(), @@ -303,6 +374,11 @@ impl Scenario { /// /// The output parses to an equal scenario, which is what lets a scenario /// be stored and reloaded without drift. + /// + /// 以 [`Scenario::parse`] 所读取的文本形式呈现场景。 + /// + /// 输出解析回相等的场景,这正是场景可被存储并重新加载而不漂移的 + /// 原因。 pub fn to_text(&self) -> String { let mut text = String::new(); text.push_str(&format!("scenario {}\n", self.name)); @@ -338,6 +414,7 @@ impl Scenario { )); // A task with no window leaves both ends infinite, which the // text form omits rather than writing an infinity. + // 没有窗口的任务使两端均为无限,文本形式省略它而不写出无限。 if task.earliest_start.is_finite() || task.latest_end.is_finite() { let start = if task.earliest_start.is_finite() { task.earliest_start @@ -399,16 +476,28 @@ impl fmt::Display for Scenario { } /// What a scenario holds, as counts and totals. +/// +/// 场景所持有的内容,以计数与总计表示。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct ScenarioSummary { /// The number of resources. + /// + /// 资源的数量。 pub resources: usize, /// The number of tasks. + /// + /// 任务的数量。 pub tasks: usize, /// The sum of the task durations, in seconds. + /// + /// 各任务时长之和,单位为秒。 pub total_task_duration: f64, /// The number of precedence constraints. + /// + /// 先后约束的数量。 pub precedence_edges: usize, /// The sum of the resource availability, in seconds. + /// + /// 各资源可用时长之和,单位为秒。 pub total_availability: f64, } diff --git a/crates/sim/apsis-sim-engine/src/engine.rs b/crates/sim/apsis-sim-engine/src/engine.rs index 327ed26..e9fcdfd 100644 --- a/crates/sim/apsis-sim-engine/src/engine.rs +++ b/crates/sim/apsis-sim-engine/src/engine.rs @@ -9,6 +9,11 @@ use apsis_event::{Event, EventId, EventQueue}; /// earliest event and passes it to a handler, which may schedule more /// events. The engine stops when the queue empties or when one of the limits /// is reached. +/// +/// 离散事件仿真引擎。 +/// +/// 引擎拥有事件队列与时钟。一次运行反复取出最早的事件并交给处理器, +/// 处理器可再调度更多事件。队列排空或达到某个限制时引擎停止。 #[derive(Debug, Clone)] pub struct Engine { queue: EventQueue, @@ -32,6 +37,12 @@ impl Engine { /// right default for a model that terminates. A model that schedules /// without bound needs a limit, which is set with /// [`Engine::with_time_limit`] or [`Engine::with_event_limit`]. + /// + /// 创建时钟为零且无限制的引擎。 + /// + /// 无限制的引擎一直运行到队列排空,这是会终止的模型的恰当缺省。 + /// 无界地调度事件的模型需要限制,可用 [`Engine::with_time_limit`] + /// 或 [`Engine::with_event_limit`] 设置。 pub const fn new() -> Self { Self { queue: EventQueue::new(), @@ -48,6 +59,11 @@ impl Engine { /// An event scheduled beyond the limit is left in the queue rather than /// executed, so the caller can examine what remained. A limit that is /// negative or not finite disables the limit. + /// + /// 设置引擎将处理的最晚仿真时刻。 + /// + /// 调度到超出该限制的事件被留在队列中而不执行,使调用方能检查 + /// 剩余了哪些。限制为负或非有限时该限制被禁用。 pub const fn with_time_limit(mut self, limit: f64) -> Self { self.time_limit = if limit.is_finite() && limit >= 0.0 { Some(limit) @@ -61,12 +77,18 @@ impl Engine { /// /// A limit of zero disables the rule, because a run that executes no /// events is never useful. + /// + /// 设置引擎将执行的事件数上限。 + /// + /// 限制为零时该规则被禁用,因为不执行任何事件的运行从无用处。 pub const fn with_event_limit(mut self, limit: u64) -> Self { self.event_limit = if limit == 0 { None } else { Some(limit) }; self } /// Returns the current simulation time, in seconds. + /// + /// 返回当前仿真时刻,单位为秒。 pub const fn now(&self) -> f64 { self.now } @@ -79,6 +101,13 @@ impl Engine { /// that event late. The engine has no way to detect the mistake, so the /// caller is responsible for keeping the clock consistent with the /// queue. + /// + /// 设置当前仿真时刻。 + /// + /// 用于从某个偏移开始运行,例如从场景历元而非从零开始。它不重排 + /// 待处理事件,故把时钟前移越过某待处理事件的调用方会迟执行该 + /// 事件。引擎无法检测这一错误,故保持时钟与队列一致是调用方的 + /// 责任。 pub fn set_now(&mut self, time: f64) { if time.is_finite() && time >= 0.0 { self.now = time; @@ -86,11 +115,15 @@ impl Engine { } /// Returns the number of events executed so far. + /// + /// 返回迄今已执行的事件数。 pub const fn executed(&self) -> u64 { self.executed } /// Returns the number of pending events. + /// + /// 返回待处理事件的数量。 pub fn pending(&self) -> usize { self.queue.len() } @@ -103,11 +136,15 @@ impl Engine { } /// Returns the time of the next pending event, if there is one. + /// + /// 返回下一待处理事件的时间(若存在)。 pub fn next_time(&self) -> Option { self.queue.next_time() } /// Returns what the engine currently holds. + /// + /// 返回引擎当前所持有的状态。 pub fn state(&self) -> EngineState { EngineState { now: self.now, @@ -121,6 +158,10 @@ impl Engine { /// /// Returns the identifier of the new event, or `None` when the time is /// not usable. + /// + /// 在某个时刻调度一个事件。 + /// + /// 返回新事件的标识符;该时刻不可用时返回 `None`。 pub fn schedule(&mut self, time: f64, payload: T) -> Option { self.queue.schedule(time, payload) } @@ -129,6 +170,10 @@ impl Engine { /// /// Returns the identifier of the new event, or `None` when the delay is /// not usable. + /// + /// 在当前仿真时刻之后某个延时处调度一个事件。 + /// + /// 返回新事件的标识符;该延时不可用时返回 `None`。 pub fn schedule_after(&mut self, delay: f64, payload: T) -> Option { self.queue.schedule_after(self.now, delay, payload) } @@ -138,27 +183,40 @@ impl Engine { /// The handler calls this when its own model says the run is over, for /// example when a stopping condition that the queue cannot express is /// met. The pending events are left in the queue. + /// + /// 请求引擎在当前事件之后停止。 + /// + /// 当处理器自身的模型表明运行已结束时调用它,例如当某个队列无法 + /// 表达的停止条件被满足时。待处理事件被留在队列中。 pub fn request_stop(&mut self) { self.stop_requested = true; } /// Clears the stop request, so that a subsequent run continues. + /// + /// 清除停止请求,使后续运行得以继续。 pub fn clear_stop(&mut self) { self.stop_requested = false; } /// Removes every pending event and returns how many were discarded. + /// + /// 移除所有待处理事件并返回被丢弃的数量。 pub fn clear(&mut self) -> usize { self.queue.clear() } /// Returns the queue, for a caller that needs to inspect or manipulate it /// directly. + /// + /// 返回事件队列,供需要直接查看或操作它的调用方使用。 pub const fn queue(&self) -> &EventQueue { &self.queue } /// Returns the queue for modification. + /// + /// 返回事件队列以供修改。 pub const fn queue_mut(&mut self) -> &mut EventQueue { &mut self.queue } @@ -173,6 +231,15 @@ impl Engine { /// Returns the outcome of the run. The clock is left at the time of the /// last event executed, not at a limit, so a caller that records the /// clock sees the last instant the model actually reached. + /// + /// 运行引擎直到其停止。 + /// + /// 每次迭代取出最早的事件,把时钟推进到其时刻,并交给处理器。处理器 + /// 可再调度更多事件,包括在同一瞬时的,它们在该瞬时已调度的事件 + /// 之后投递。 + /// + /// 返回该运行的结果。时钟留在最后执行事件的时间,而非某个限制处, + /// 故记录时钟的调用方看到的是模型实际到达的最后瞬时。 pub fn run(&mut self, mut handler: F) -> EngineOutcome where F: FnMut(&mut Engine, Event), @@ -185,6 +252,8 @@ impl Engine { // The event limit is checked before taking an event, so a run // that reaches the limit leaves the queue untouched for the next // call. + // 事件限制在取出事件之前检查,故达到限制的运行会把队列原样 + // 留待下次调用。 if let Some(limit) = self.event_limit { if self.executed >= limit { break StopReason::EventLimit; @@ -198,6 +267,8 @@ impl Engine { // The time limit leaves the event in the queue rather than // discarding it, so the caller can inspect what remained. The // event was already popped, so it is put back. + // 时限把事件留在队列中而非丢弃,使调用方能检查剩余了哪些。 + // 该事件已被弹出,故将其放回。 if let Some(limit) = self.time_limit { if event.time > limit { self.queue.schedule(event.time, event.payload); diff --git a/crates/sim/apsis-sim-engine/src/outcome.rs b/crates/sim/apsis-sim-engine/src/outcome.rs index b2b3a4e..37e482b 100644 --- a/crates/sim/apsis-sim-engine/src/outcome.rs +++ b/crates/sim/apsis-sim-engine/src/outcome.rs @@ -7,27 +7,44 @@ use core::fmt; /// The reason is reported rather than inferred from the event count, because /// the same count can arise from an empty queue or from a limit, and a /// caller that records the outcome of a run needs to distinguish them. +/// +/// 一次运行停止的原因。 +/// +/// 该原因是被报告的,而不是由事件计数推断的,因为同样的计数可能来自 +/// 空队列或某个限制,而记录运行结果的调用方需要区分二者。 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum StopReason { /// No events were left when the engine looked for the next one. + /// + /// 引擎寻找下一事件时已无事件剩余。 QueueEmpty, /// The time of the next event is beyond the simulation time limit. + /// + /// 下一事件的时间超出仿真时限。 TimeLimit, /// The handler set a flag asking the engine to stop. + /// + /// 处理器置位标志,要求引擎停止。 Requested, /// The event count reached the configured limit. + /// + /// 事件计数达到所配置的上限。 EventLimit, } impl StopReason { /// Returns `true` when the run ended because the model had no more work /// to do rather than because a limit intervened. + /// + /// 当运行因模型已无工作而结束、而非因某个限制介入时返回 `true`。 pub const fn is_natural(self) -> bool { matches!(self, Self::QueueEmpty) } /// Returns a short name for the reason. + /// + /// 返回该原因的简称。 pub const fn name(self) -> &'static str { match self { Self::QueueEmpty => "queue empty", @@ -48,22 +65,38 @@ impl fmt::Display for StopReason { /// /// The outcome records the counts and the clock, so a caller can record the /// result of a run without reaching back into the engine. +/// +/// 一次运行所产生的结果。 +/// +/// 结果记录各项计数与时钟,使调用方无需回访引擎即可记录一次运行的结果。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct EngineOutcome { /// Why the run stopped. + /// + /// 该运行停止的原因。 pub reason: StopReason, /// The number of events the run executed. + /// + /// 该运行所执行的事件数。 pub executed: u64, /// The simulation time the run started from, in seconds. + /// + /// 该运行的起始仿真时刻,单位为秒。 pub started_at: f64, /// The simulation time the run reached, in seconds. + /// + /// 该运行所到达的仿真时刻,单位为秒。 pub ended_at: f64, /// The number of events still pending when the run stopped. + /// + /// 该运行停止时仍待处理的事件数。 pub remaining: usize, } impl EngineOutcome { /// Returns the time the run covered, in seconds. + /// + /// 返回该运行所覆盖的时长,单位为秒。 pub fn duration(&self) -> f64 { self.ended_at - self.started_at } @@ -72,6 +105,11 @@ impl EngineOutcome { /// /// This measures how busy the model is rather than how long the run took /// on the wall clock. Returns `None` when the run covered no time. + /// + /// 返回每仿真秒的平均事件数。 + /// + /// 它衡量模型的繁忙程度,而非该运行在挂钟上耗时多久。运行未覆盖 + /// 任何时间时返回 `None`。 pub fn event_rate(&self) -> Option { let duration = self.duration(); if duration > 0.0 { @@ -83,6 +121,8 @@ impl EngineOutcome { /// Returns `true` when the run finished because the model had no more /// work rather than because a limit stopped it. + /// + /// 当运行因模型已无工作而结束、而非因某个限制而停止时返回 `true`。 pub fn is_natural(&self) -> bool { self.reason.is_natural() } @@ -99,15 +139,25 @@ impl fmt::Display for EngineOutcome { } /// What the engine currently holds. +/// +/// 引擎当前所持有的状态。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct EngineState { /// The current simulation time, in seconds. + /// + /// 当前仿真时刻,单位为秒。 pub now: f64, /// The number of pending events. + /// + /// 待处理事件的数量。 pub pending: usize, /// The number of events executed so far. + /// + /// 迄今已执行的事件数。 pub executed: u64, /// The time of the next pending event, if there is one. + /// + /// 下一待处理事件的时间(若存在)。 pub next_time: Option, } diff --git a/crates/sim/apsis-sim/src/activity.rs b/crates/sim/apsis-sim/src/activity.rs index 360b3ac..2704f21 100644 --- a/crates/sim/apsis-sim/src/activity.rs +++ b/crates/sim/apsis-sim/src/activity.rs @@ -8,33 +8,53 @@ use apsis_task::TaskId; /// and an end, and the handler is called at each. The two are separate events /// rather than one interval so that the engine orders them with everything /// else on the clock, which is what lets two tasks overlap correctly. +/// +/// 时间线上发生的一件事。 +/// +/// 时间线由一个调度导出:一处被放置的任务贡献一个开始与一个结束, +/// 处理器在每个处被调用。二者是分开的事件而非一个区间,使引擎把它们 +/// 与时钟上其余一切一起排序,这正是让两个任务正确重叠的原因。 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum Activity { /// A task begins. + /// + /// 某任务开始。 Start { /// The task beginning. + /// + /// 正在开始的任务。 task: TaskId, }, /// A task ends. + /// + /// 某任务结束。 End { /// The task ending. + /// + /// 正在结束的任务。 task: TaskId, }, } impl Activity { /// Creates a start activity. + /// + /// 创建一个开始活动。 pub const fn start(task: TaskId) -> Self { Self::Start { task } } /// Creates an end activity. + /// + /// 创建一个结束活动。 pub const fn end(task: TaskId) -> Self { Self::End { task } } /// Returns the task the activity is about. + /// + /// 返回该活动所关于的任务。 pub const fn task(self) -> TaskId { match self { Self::Start { task } | Self::End { task } => task, @@ -42,11 +62,15 @@ impl Activity { } /// Returns `true` when the activity is a start. + /// + /// 当该活动为开始时返回 `true`。 pub const fn is_start(self) -> bool { matches!(self, Self::Start { .. }) } /// Returns `true` when the activity is an end. + /// + /// 当该活动为结束时返回 `true`。 pub const fn is_end(self) -> bool { matches!(self, Self::End { .. }) } diff --git a/crates/sim/apsis-sim/src/session.rs b/crates/sim/apsis-sim/src/session.rs index 48dc561..3a69c4c 100644 --- a/crates/sim/apsis-sim/src/session.rs +++ b/crates/sim/apsis-sim/src/session.rs @@ -10,30 +10,47 @@ use apsis_sim_engine::{Engine, EngineOutcome}; /// The report pairs the engine's own outcome with the output trace, so a /// caller has everything a run produced without reaching back into the /// session. +/// +/// 一次运行所产生的结果。 +/// +/// 该报告把引擎自身的结果与输出轨迹配对,使调用方无需回访会话即可得到 +/// 一次运行所产生的全部内容。 #[derive(Debug, Clone, PartialEq)] pub struct RunReport { /// What the engine did. + /// + /// 引擎所做的。 pub outcome: EngineOutcome, /// The records the run made. + /// + /// 该运行所作出的记录。 pub trace: Trace, /// The number of records the recorder refused because they were not /// usable. + /// + /// 记录器因不可用而拒绝的记录数。 pub refused: u64, } impl RunReport { /// Returns the number of activities the run executed. + /// + /// 返回该运行所执行的活动数。 pub const fn executed(&self) -> u64 { self.outcome.executed } /// Returns `true` when the run finished because the timeline was /// exhausted rather than because a limit stopped it. + /// + /// 当运行因时间线耗尽而结束、而非因某个限制而停止时返回 `true`。 pub fn is_complete(&self) -> bool { self.outcome.is_natural() } /// Returns the number of warnings recorded. + /// + /// 返回所记录的警告数。 pub fn warning_count(&self) -> usize { self.trace.warning_count() } @@ -44,6 +61,11 @@ impl RunReport { /// A session owns the engine and the recorder for a single run, so it can be /// driven from one place without the caller holding either. It is created by /// [`Simulation::start`]. +/// +/// 一次已准备仿真的运行。 +/// +/// 会话为单次运行拥有引擎与记录器,故它可在一处被驱动而无需调用方持有 +/// 二者中的任何一个。它由 [`Simulation::start`] 创建。 #[derive(Debug, Clone)] pub struct Session { engine: Engine, @@ -52,6 +74,8 @@ pub struct Session { impl Session { /// Builds a session from a prepared simulation. + /// + /// 由已准备的仿真构建会话。 pub(crate) fn new(simulation: &Simulation) -> Self { let scenario = simulation.scenario(); let mut engine = Engine::new() @@ -61,6 +85,8 @@ impl Session { // The timeline is relative to the scenario origin, so the epoch is // added to every event time. Without the offset the engine would // move the clock backwards when it delivered the first event. + // 时间线相对于场景原点,故历元被加到每个事件时间上。若无该偏移, + // 引擎在投递第一个事件时会把时钟往回移。 let epoch = scenario.epoch(); for (time, activity) in simulation.timeline() { engine.schedule(epoch + time, *activity); @@ -72,26 +98,36 @@ impl Session { } /// Returns the engine, for a caller that needs its state between calls. + /// + /// 返回引擎,供需要在两次调用之间获取其状态的调用方使用。 pub const fn engine(&self) -> &Engine { &self.engine } /// Returns the engine for modification. + /// + /// 返回引擎以供修改。 pub const fn engine_mut(&mut self) -> &mut Engine { &mut self.engine } /// Returns the recorder, for a caller that reads the trace as it grows. + /// + /// 返回记录器,供在轨迹增长时读取它的调用方使用。 pub const fn recorder(&self) -> &Recorder { &self.recorder } /// Returns the recorder for modification. + /// + /// 返回记录器以供修改。 pub const fn recorder_mut(&mut self) -> &mut Recorder { &mut self.recorder } /// Returns the current simulation time, in seconds. + /// + /// 返回当前仿真时刻,单位为秒。 pub const fn now(&self) -> f64 { self.engine.now() } @@ -104,6 +140,14 @@ impl Session { /// /// Returns a report holding the engine outcome and the trace made during /// this call. The recorder is left empty for the next call. + /// + /// 运行引擎,在每个活动处调用处理器。 + /// + /// 处理器接收引擎、记录器与活动。它通常为该活动记录些什么,并可 + /// 再调度更多事件;引擎把它们与时间线一起按时间次序投递。 + /// + /// 返回一个报告,其中含有引擎结果与本次调用期间所作出的轨迹。记录器 + /// 被留空以备下次调用。 pub fn run(&mut self, mut handler: F) -> RunReport where F: FnMut(&mut Engine, &mut Recorder, apsis_event::Event), @@ -122,11 +166,17 @@ impl Session { /// /// This is for a caller that drives the engine itself and only wants the /// recorder. + /// + /// 把某个活动记录为状态变更而不运行任何东西。 + /// + /// 这适用于自行驱动引擎而只想要记录器的调用方。 pub fn note(&mut self, time: f64, label: impl Into, value: f64) -> bool { self.recorder.note(time, label, value) } /// Records a value of a given kind without running anything. + /// + /// 记录给定类别的一个值而不运行任何东西。 pub fn record( &mut self, time: f64, diff --git a/crates/sim/apsis-sim/src/simulation.rs b/crates/sim/apsis-sim/src/simulation.rs index 49a2b2c..27f26ed 100644 --- a/crates/sim/apsis-sim/src/simulation.rs +++ b/crates/sim/apsis-sim/src/simulation.rs @@ -14,6 +14,11 @@ use apsis_task::{Task, TaskId}; /// 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, @@ -30,6 +35,13 @@ impl Simulation { /// 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) @@ -50,6 +62,11 @@ impl Simulation { /// 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, @@ -66,6 +83,8 @@ impl Simulation { } /// Builds the timeline from a plan. + /// + /// 由计划构建时间线。 fn from_parts( scenario: Scenario, resources: ResourceSet, @@ -83,6 +102,7 @@ impl Simulation { }; // 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!( @@ -102,26 +122,36 @@ impl Simulation { } /// 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() } @@ -131,11 +161,19 @@ impl Simulation { /// 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() } @@ -148,26 +186,36 @@ impl Simulation { } /// 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) @@ -179,6 +227,11 @@ impl Simulation { /// 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) }