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}