reactor_rt/scheduler/
events.rs

1use std::borrow::Cow;
2use std::collections::VecDeque;
3use std::fmt::{Display, Formatter};
4use std::time::Instant;
5
6use super::ReactionPlan;
7use crate::scheduler::dependencies::{DataflowInfo, ExecutableReactions};
8use crate::triggers::TriggerId;
9use crate::*;
10
11/// The tag of an event.
12///
13/// Tags correspond to a point on the logical timeline, and also
14/// implement *superdense time*, which means an
15/// infinite sequence of tags may be processed for any logical
16/// instant. The label on this sequence is called the *microstep*
17/// of the tag.
18///
19/// Use the [tag!](crate::tag) macro to create this struct with
20/// convenient syntax.
21#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug, Ord, PartialOrd)]
22pub struct EventTag {
23    /// The time offset from the origin of the logical timeline.
24    /// Knowing the start time of the application is necessary to
25    /// convert this to an absolute [Instant] (see [Self::to_logical_time]).
26    pub offset_from_t0: Duration,
27    /// The microstep of this tag.
28    pub microstep: MicroStep,
29}
30
31impl EventTag {
32    /// The tag of the startup event.
33    pub const ORIGIN: EventTag = EventTag {
34        offset_from_t0: Duration::from_millis(0),
35        microstep: MicroStep::ZERO,
36    };
37
38    /// Returns the logical instant for this tag, using the
39    /// initial time `t0`.
40    #[inline]
41    pub fn to_logical_time(&self, t0: Instant) -> Instant {
42        t0 + self.offset_from_t0
43    }
44
45    /// Returns the amount of time elapsed since the start
46    /// of the app.
47    ///
48    /// ```no_run
49    /// # use std::time::Instant;
50    /// # use reactor_rt::EventTag;
51    /// # let t0: Instant = unimplemented!();
52    /// # let tag1: EventTag = unimplemented!();
53    /// assert_eq!(tag1.duration_since_start(), tag1.to_logical_time(t0) - t0)
54    /// ```
55    #[inline]
56    pub fn duration_since_start(&self) -> Duration {
57        self.offset_from_t0
58    }
59
60    /// Returns the microstep of this tag.
61    #[inline]
62    pub fn microstep(&self) -> MicroStep {
63        self.microstep
64    }
65
66    /// Create a tag for the zeroth microstep of the given instant.
67    #[inline]
68    pub(crate) fn absolute(t0: Instant, instant: Instant) -> Self {
69        Self {
70            offset_from_t0: instant - t0,
71            microstep: MicroStep::ZERO,
72        }
73    }
74
75    /// Create a new tag from its offset from t0 and a microstep.
76    /// Use the [tag!](crate::tag) macro for more convenient syntax.
77    #[inline]
78    pub fn offset(offset_from_t0: Duration, microstep: crate::time::MS) -> Self {
79        Self {
80            offset_from_t0,
81            microstep: MicroStep::new(microstep),
82        }
83    }
84
85    /// Returns a tag that is strictly greater than this one.
86    #[inline]
87    pub(crate) fn successor(self, offset: Duration) -> Self {
88        if offset.is_zero() {
89            self.next_microstep()
90        } else {
91            Self {
92                offset_from_t0: self.offset_from_t0 + offset,
93                microstep: MicroStep::ZERO,
94            }
95        }
96    }
97
98    #[inline]
99    pub(crate) fn next_microstep(&self) -> Self {
100        Self {
101            offset_from_t0: self.offset_from_t0,
102            microstep: self.microstep + 1,
103        }
104    }
105
106    #[inline]
107    pub(crate) fn now(t0: Instant) -> Self {
108        Self {
109            offset_from_t0: Instant::now() - t0,
110            microstep: MicroStep::ZERO,
111        }
112    }
113}
114
115impl Display for EventTag {
116    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
117        let elapsed = self.offset_from_t0;
118        write!(
119            f,
120            "(T0 + {} ns = {} ms, {})",
121            elapsed.as_nanos(),
122            elapsed.as_millis(),
123            self.microstep
124        )
125    }
126}
127
128/// A tagged event of the reactor program. Events are tagged
129/// with the logical instant at which they must be processed.
130/// They are queued and processed in order. See [self::EventQueue].
131///
132/// [self::AsyncCtx] may only communicate with
133/// the scheduler by sending events.
134#[derive(Debug)]
135pub(super) struct Event<'x> {
136    /// The tag at which the reactions to this event must be executed.
137    /// This is always > to the latest *processed* tag, by construction
138    /// of the reactor application.
139    pub(super) tag: EventTag,
140    /// A set of reactions to execute.
141    pub reactions: ReactionPlan<'x>,
142    /// Whether we should terminate the application at
143    /// the tag of this event (after processing the tag).
144    pub terminate: bool,
145}
146
147impl<'x> Event<'x> {
148    pub fn absorb(&mut self, other: Event<'x>) {
149        debug_assert_eq!(self.tag, other.tag);
150        self.reactions = ExecutableReactions::merge_cows(self.reactions.take(), other.reactions);
151        self.terminate |= other.terminate;
152    }
153
154    pub fn execute(tag: EventTag, reactions: Cow<'x, ExecutableReactions<'x>>) -> Self {
155        Self { tag, reactions: Some(reactions), terminate: false }
156    }
157    pub fn terminate_at(tag: EventTag) -> Self {
158        Self { tag, reactions: None, terminate: true }
159    }
160}
161
162/// An event sent by a physical action from an asynchronous
163/// thread. This is distinct from [Event] so as not to have
164/// to send references, which require quantifying the lifetime
165/// of the event and event queue and everything.
166pub(super) struct PhysicalEvent {
167    /// The tag.
168    pub tag: EventTag,
169    /// The ID of the physical action that triggered this event.
170    pub trigger_id: Option<TriggerId>,
171    pub terminate: bool,
172}
173
174impl PhysicalEvent {
175    /// Turn a [PhysicalEvent] into an [Event] within the scheduler.
176    pub(super) fn make_executable(self, dataflow: &DataflowInfo) -> Event {
177        let PhysicalEvent { tag, trigger_id, terminate } = self;
178        Event {
179            tag,
180            terminate,
181            reactions: trigger_id.map(|id| Cow::Borrowed(dataflow.reactions_triggered_by(&id))),
182        }
183    }
184
185    pub fn trigger(tag: EventTag, trigger: TriggerId) -> Self {
186        Self { tag, trigger_id: Some(trigger), terminate: false }
187    }
188    pub fn terminate_at(tag: EventTag) -> Self {
189        Self { tag, trigger_id: None, terminate: true }
190    }
191}
192
193/// A queue of pending [Event]s. Events are ordered by tag,
194/// so this is not a FIFO queue.
195#[derive(Default)]
196pub(super) struct EventQueue<'x> {
197    /// This list is sorted by the tag of each event (in ascending order).
198    ///
199    /// But insertion is at worse O(n)... And it's easy to build
200    /// a pathological program where this worse case is always hit.
201    /// Theoretically using a tree/ heap would be useful here.
202    ///        .
203    ///       ..
204    ///      ...
205    ///     ....
206    value_list: VecDeque<Event<'x>>,
207}
208
209impl<'x> EventQueue<'x> {
210    /// Removes and returns the earliest tag
211    pub fn take_earliest(&mut self) -> Option<Event<'x>> {
212        self.value_list.pop_front()
213    }
214
215    // todo perf: we could make a more optimal function to push a
216    //  lot of events at once. Consider the following algorithm:
217    //  - start with a sorted `self.value_list` and a (non-sorted) `new_evts: Vec<Event>`
218    //  - sort the new events in place (in a Cow maybe). They'll
219    //  probably come in already sorted but we can't assume this.
220    //  Use an algorithm that best-cases for sorted data. (eg https://crates.io/crates/dmsort)
221    //  - take the earliest new event and binary search to insert it.
222    //  - then do the same thing but only on the remaining (to the right)
223    //  portion of `self.value_list`. Basically the routine of an insertion
224    //  sort.
225
226    /// Push an event into the heap.
227    pub(super) fn push(&mut self, evt: Event<'x>) {
228        match self.value_list.binary_search_by_key(&evt.tag, |e| e.tag) {
229            Ok(idx) => self.value_list[idx].absorb(evt),
230            Err(idx) => self.value_list.insert(idx, evt),
231        }
232    }
233}