1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
use std::cmp::{Ord, Ordering, PartialOrd};
use std::collections::BinaryHeap;
use std::sync::{
mpsc::{channel, Receiver, Sender},
Mutex, Once,
};
use std::time::{Duration, Instant};
use uuid::Uuid;
use lazy_static::lazy_static;
static ONCE: Once = Once::new();
lazy_static! {
static ref CHANNEL: (Mutex<Sender<ToastEvent>>, Mutex<Receiver<ToastEvent>>) = {
let (sender, receiver) = channel();
(Mutex::new(sender), Mutex::new(receiver))
};
static ref TIMER_CHANNEL: (Mutex<Sender<FutureEvent>>, Mutex<Receiver<FutureEvent>>) = {
let (sender, receiver) = channel();
(Mutex::new(sender), Mutex::new(receiver))
};
}
struct FutureEvent
{
instant: Instant,
event: ToastEvent,
}
impl Ord for FutureEvent
{
fn cmp(&self, other: &Self) -> Ordering
{
// Reverse the comparison. The smallest instant should be the one
// with the highest priority.
other.instant.cmp(&self.instant)
}
}
impl PartialOrd for FutureEvent
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering>
{
Some(self.cmp(other))
}
}
impl Eq for FutureEvent {}
impl PartialEq for FutureEvent
{
fn eq(&self, other: &Self) -> bool
{
self.instant.eq(&other.instant)
}
}
#[derive(Debug)]
pub enum ToastEvent
{
Show
{
uuid: Uuid,
text: String,
error: bool,
},
Close
{
uuid: Uuid
},
}
pub fn recv() -> ToastEvent
{
// Hold a lock on the receiver to allow only one thread to receive at a time.
//
// Ideally only one thread should be responsible for receiving these messages,
// but who knows what's going to happen here.
let receiver = CHANNEL.1.lock().expect("Mutex poisoned");
receiver.recv().unwrap()
}
pub fn show_message<T: ToString>(text: T)
{
show_toast(text.to_string(), false);
}
pub fn show_error<T: ToString>(text: T)
{
// We'll want to log the errors into the log as well.
let string = text.to_string();
log::error!("{}", string);
show_toast(string, true);
}
fn show_toast(text: String, error: bool)
{
ensure_running();
let uuid = Uuid::new_v4();
let primary_sender = CHANNEL.0.lock().expect("Mutex poisoned").clone();
let timer_sender = TIMER_CHANNEL.0.lock().expect("Mutex poisoned").clone();
primary_sender
.send(ToastEvent::Show { uuid, text, error })
.unwrap();
timer_sender
.send(FutureEvent {
instant: Instant::now() + Duration::from_secs(5),
event: ToastEvent::Close { uuid },
})
.unwrap();
}
fn ensure_running()
{
ONCE.call_once(|| {
std::mem::forget(std::thread::spawn(|| {
// This thread should hold the lock on the receiver all the time.
// There's no need for anyone else to read these messages.
let receiver = TIMER_CHANNEL.1.lock().expect("Mutex poisoned");
let sender = CHANNEL.0.lock().expect("Mutex poisoned").clone();
// Collection of future events to send.
let mut events = BinaryHeap::new();
// The outer loop handles the case where there are no existing events in the map.
while let Ok(e) = receiver.recv() {
let mut next_timeout = e.instant - Instant::now();
events.push(e);
// The inner loop is responsible for waiting for events to resolve.
loop {
match receiver.recv_timeout(next_timeout) {
Ok(new_event) => {
// Received a new event before the timeout occurred.
// Push the event into the queue and resolve the next timeout again.
events.push(new_event);
next_timeout = events.peek().unwrap().instant - Instant::now();
}
Err(_) => {
// Timeout happened. Pop the next event and send it.
let next_event = events.pop().unwrap();
sender
.send(next_event.event)
.expect("Toast receiver has died");
// If there are moer events in the queue, wait for them to resolve.
// Otherwise exit the inner loop to go back to the outer one that waits for
// events without a timeout.
match events.peek() {
Some(e) => next_timeout = e.instant - Instant::now(),
None => break,
}
}
}
}
}
}));
});
}