docs: bilingual comments for the simulation layer
The event queue and identifiers, the output records and trace, the scenario build and parse, and the run facade, including the plain comments inside the functions.
This commit is contained in:
parent
30c9a348c3
commit
891fe0b37b
|
|
@ -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<Self> {
|
||||
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<T> {
|
||||
/// 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<T> Event<T> {
|
||||
/// 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<U>(self, payload: U) -> Event<U> {
|
||||
Event {
|
||||
id: self.id,
|
||||
|
|
@ -98,6 +139,10 @@ impl<T> Event<T> {
|
|||
///
|
||||
/// 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<Event<T>>
|
||||
where
|
||||
T: Clone,
|
||||
|
|
|
|||
|
|
@ -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<f64>,
|
||||
/// 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<T> {
|
||||
pending: BinaryHeap<Scheduled>,
|
||||
|
|
@ -85,6 +109,8 @@ impl<T> Default for EventQueue<T> {
|
|||
|
||||
impl<T> EventQueue<T> {
|
||||
/// Creates an empty queue.
|
||||
///
|
||||
/// 创建一个空队列。
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
pending: BinaryHeap::new(),
|
||||
|
|
@ -95,6 +121,8 @@ impl<T> EventQueue<T> {
|
|||
}
|
||||
|
||||
/// Returns the number of pending events.
|
||||
///
|
||||
/// 返回待处理事件的数量。
|
||||
pub fn len(&self) -> usize {
|
||||
self.pending.len()
|
||||
}
|
||||
|
|
@ -107,11 +135,15 @@ impl<T> EventQueue<T> {
|
|||
}
|
||||
|
||||
/// 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<T> EventQueue<T> {
|
|||
/// Returns the time of the earliest pending event.
|
||||
///
|
||||
/// Returns `None` when the queue is empty.
|
||||
///
|
||||
/// 返回最早待处理事件的时间。
|
||||
///
|
||||
/// 队列为空时返回 `None`。
|
||||
pub fn next_time(&self) -> Option<f64> {
|
||||
self.pending.peek().map(|entry| entry.time())
|
||||
}
|
||||
|
|
@ -135,6 +171,14 @@ impl<T> EventQueue<T> {
|
|||
/// 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<EventId> {
|
||||
let sequence = self.next_sequence;
|
||||
let id = EventId::new(time, sequence)?;
|
||||
|
|
@ -146,6 +190,8 @@ impl<T> EventQueue<T> {
|
|||
// 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<T> EventQueue<T> {
|
|||
/// 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<EventId> {
|
||||
if !delay.is_finite() || delay < 0.0 {
|
||||
return None;
|
||||
|
|
@ -176,6 +228,11 @@ impl<T> EventQueue<T> {
|
|||
/// 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<Event<T>> {
|
||||
let entry = self.pending.pop()?;
|
||||
let index = entry.sequence as usize;
|
||||
|
|
@ -194,6 +251,8 @@ impl<T> EventQueue<T> {
|
|||
|
||||
/// 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<T> EventQueue<T> {
|
|||
///
|
||||
/// 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<T> EventQueue<T> {
|
|||
/// 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<Event<T>> {
|
||||
let mut events = Vec::with_capacity(self.pending.len());
|
||||
while let Some(event) = self.pop() {
|
||||
|
|
|
|||
|
|
@ -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<String>, value: f64) -> Self {
|
||||
Self {
|
||||
time,
|
||||
|
|
@ -68,16 +97,22 @@ impl Record {
|
|||
}
|
||||
|
||||
/// Creates an observation record.
|
||||
///
|
||||
/// 创建一条观测记录。
|
||||
pub fn observation(time: f64, label: impl Into<String>, value: f64) -> Self {
|
||||
Self::new(time, RecordKind::Observation, label, value)
|
||||
}
|
||||
|
||||
/// Creates a state record.
|
||||
///
|
||||
/// 创建一条状态记录。
|
||||
pub fn state(time: f64, label: impl Into<String>, value: f64) -> Self {
|
||||
Self::new(time, RecordKind::State, label, value)
|
||||
}
|
||||
|
||||
/// Creates a warning record.
|
||||
///
|
||||
/// 创建一条警告记录。
|
||||
pub fn warning(time: f64, label: impl Into<String>, 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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String>, value: f64) -> bool {
|
||||
self.record(time, RecordKind::Observation, label, value)
|
||||
}
|
||||
|
||||
/// Records a state change.
|
||||
///
|
||||
/// 记录一次状态变更。
|
||||
pub fn note(&mut self, time: f64, label: impl Into<String>, value: f64) -> bool {
|
||||
self.record(time, RecordKind::State, label, value)
|
||||
}
|
||||
|
||||
/// Records a warning.
|
||||
///
|
||||
/// 记录一次警告。
|
||||
pub fn warn(&mut self, time: f64, label: impl Into<String>, 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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Record>,
|
||||
|
|
@ -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<Record>) -> 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<Record> {
|
||||
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<f64> {
|
||||
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<f64> {
|
||||
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<SeriesSummary> {
|
||||
let values: Vec<f64> = 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<SeriesSummary> {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<usize> {
|
||||
self.line
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Scenario, ScenarioError> {
|
||||
let lines = collect_lines(text);
|
||||
let mut scenario = parse_resources(&lines)?;
|
||||
|
|
@ -28,6 +39,8 @@ pub(crate) fn parse(text: &str) -> Result<Scenario, ScenarioError> {
|
|||
|
||||
/// 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<Line<'_>> {
|
||||
text.lines()
|
||||
.enumerate()
|
||||
|
|
@ -120,11 +143,15 @@ fn collect_lines(text: &str) -> Vec<Line<'_>> {
|
|||
}
|
||||
|
||||
/// 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<Scenario, ScenarioError> {
|
||||
let mut name: Option<String> = None;
|
||||
let mut scenario: Option<Scenario> = None;
|
||||
|
|
@ -136,6 +163,7 @@ fn parse_resources(lines: &[Line<'_>]) -> Result<Scenario, ScenarioError> {
|
|||
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<Scenario, ScenarioError> {
|
|||
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<Scenario, ScenarioError> {
|
|||
}
|
||||
|
||||
/// The task pass.
|
||||
///
|
||||
/// 任务那一遍。
|
||||
fn parse_tasks(lines: &[Line<'_>], scenario: &mut Scenario) -> Result<(), ScenarioError> {
|
||||
let mut current: Option<Task> = 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<Task>, scenario: &mut Scenario) -> Result<(), ScenarioError> {
|
||||
if let Some(task) = current.take() {
|
||||
scenario.add_task(task)?;
|
||||
|
|
@ -283,6 +316,8 @@ fn flush(current: &mut Option<Task>, 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<Task>,
|
||||
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" {
|
||||
|
|
|
|||
|
|
@ -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<String>) -> 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<f64> {
|
||||
self.time_limit
|
||||
}
|
||||
|
||||
/// Returns the greatest number of events the run will execute, if one is
|
||||
/// set.
|
||||
///
|
||||
/// 返回该运行将执行的事件数上限(若已设置)。
|
||||
pub const fn event_limit(&self) -> Option<u64> {
|
||||
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<Resource> {
|
||||
&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<Self, ScenarioError> {
|
||||
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, ScenarioError> {
|
||||
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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<T> {
|
||||
queue: EventQueue<T>,
|
||||
|
|
@ -32,6 +37,12 @@ impl<T> Engine<T> {
|
|||
/// 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<T> Engine<T> {
|
|||
/// 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<T> Engine<T> {
|
|||
///
|
||||
/// 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<T> Engine<T> {
|
|||
/// 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<T> Engine<T> {
|
|||
}
|
||||
|
||||
/// 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<T> Engine<T> {
|
|||
}
|
||||
|
||||
/// Returns the time of the next pending event, if there is one.
|
||||
///
|
||||
/// 返回下一待处理事件的时间(若存在)。
|
||||
pub fn next_time(&self) -> Option<f64> {
|
||||
self.queue.next_time()
|
||||
}
|
||||
|
||||
/// Returns what the engine currently holds.
|
||||
///
|
||||
/// 返回引擎当前所持有的状态。
|
||||
pub fn state(&self) -> EngineState {
|
||||
EngineState {
|
||||
now: self.now,
|
||||
|
|
@ -121,6 +158,10 @@ impl<T> Engine<T> {
|
|||
///
|
||||
/// 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<EventId> {
|
||||
self.queue.schedule(time, payload)
|
||||
}
|
||||
|
|
@ -129,6 +170,10 @@ impl<T> Engine<T> {
|
|||
///
|
||||
/// 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<EventId> {
|
||||
self.queue.schedule_after(self.now, delay, payload)
|
||||
}
|
||||
|
|
@ -138,27 +183,40 @@ impl<T> Engine<T> {
|
|||
/// 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<T> {
|
||||
&self.queue
|
||||
}
|
||||
|
||||
/// Returns the queue for modification.
|
||||
///
|
||||
/// 返回事件队列以供修改。
|
||||
pub const fn queue_mut(&mut self) -> &mut EventQueue<T> {
|
||||
&mut self.queue
|
||||
}
|
||||
|
|
@ -173,6 +231,15 @@ impl<T> Engine<T> {
|
|||
/// 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<F>(&mut self, mut handler: F) -> EngineOutcome
|
||||
where
|
||||
F: FnMut(&mut Engine<T>, Event<T>),
|
||||
|
|
@ -185,6 +252,8 @@ impl<T> Engine<T> {
|
|||
// 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<T> Engine<T> {
|
|||
// 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);
|
||||
|
|
|
|||
|
|
@ -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<f64> {
|
||||
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<f64>,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 { .. })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Activity>,
|
||||
|
|
@ -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<Activity> {
|
||||
&self.engine
|
||||
}
|
||||
|
||||
/// Returns the engine for modification.
|
||||
///
|
||||
/// 返回引擎以供修改。
|
||||
pub const fn engine_mut(&mut self) -> &mut Engine<Activity> {
|
||||
&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<F>(&mut self, mut handler: F) -> RunReport
|
||||
where
|
||||
F: FnMut(&mut Engine<Activity>, &mut Recorder, apsis_event::Event<Activity>),
|
||||
|
|
@ -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<String>, 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,
|
||||
|
|
|
|||
|
|
@ -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<Self, SimulationError> {
|
||||
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<TaskId> {
|
||||
self.result.deferred()
|
||||
}
|
||||
|
||||
/// Returns the tasks that cannot be placed at all.
|
||||
///
|
||||
/// 返回完全无法放置的任务。
|
||||
pub fn infeasible(&self) -> Vec<TaskId> {
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue