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
use std::{
collections::VecDeque,
iter,
ops::Range,
sync::atomic::{AtomicU64, Ordering},
};
use az::CheckedAs;
use crate::{Icon, List, ListItem, ListStyle, list::ListItemCallbacks, proto};
/// Store to map list item IDs to their callbacks.
pub(crate) struct ListItemStore {
/// A deque of all queries which have been made.
///
/// Old queries are only dropped when an activation is performed, as
/// this is the only way to guarantee that old list items will not be
/// activated. Long latency from sending the query results to the front
/// end could result in old queries being activated.
///
/// This disposal implementation may change in the future.
///
/// The IDs stored in the deque should be increasing and contiguous.
queries: VecDeque<QueryListItemStore>,
ids: AutoIncrementer,
}
impl ListItemStore {
pub(crate) fn new() -> Self {
Self {
queries: VecDeque::new(),
ids: AutoIncrementer(AtomicU64::new(0)),
}
}
/// Stores the result of a query, returning the response that should be
/// sent to covey.
pub(crate) fn store_query_result(&mut self, list: List) -> proto::QueryResponse {
// Don't store an empty result
if list.items.is_empty() {
return proto::QueryResponse {
items: vec![],
list_style: list.style.map(ListStyle::into_proto),
};
}
let (items, callbacks) = split_item_vec(&self.ids, list.items);
self.queries.push_back(QueryListItemStore {
callbacks,
first_id: items.first().expect("list should be non empty").id,
});
return proto::QueryResponse {
items,
list_style: list.style.map(ListStyle::into_proto),
};
fn split_item_vec(
ids: &AutoIncrementer,
vec: Vec<ListItem>,
) -> (Vec<proto::ListItem>, Vec<ListItemCallbacks>) {
let new_ids = ids.fetch_many(vec.len() as u64);
let mut items = vec![];
let mut callbacks = vec![];
for (id, item) in iter::zip(new_ids, vec) {
items.push(proto::ListItem {
id,
title: item.title,
description: item.description,
icon: item.icon.map(Icon::into_proto),
available_commands: item.commands.ids().map(ToOwned::to_owned).collect(),
});
callbacks.push(item.commands);
}
(items, callbacks)
}
}
/// Finds the associated callbacks of an ID.
///
/// This should never return [`None`] if the ID comes from an RPC call.
/// However, implementation may change in the future which disposes of
/// callbacks more frequently, and may have extremely rare edge cases where
/// a callback is disposed but then activated.
pub(crate) fn fetch_callbacks_of(&mut self, id: u64) -> Option<ListItemCallbacks> {
// linear search is good enough
let (found_index, found_callback) =
self.queries.iter().enumerate().find_map(|(i, query)| {
query
.callback_of_id(id)
.map(|callbacks| (i, callbacks.clone()))
})?;
// Remove old queries.
// Don't include the current query, since the action could be
// nothing and the same query could have another list item activated.
self.queries.drain(..found_index);
Some(found_callback)
}
}
/// INVARIANTS:
/// - IDs of the list items are increasing and contiguous.
/// - Number of items stored is non-zero.
struct QueryListItemStore {
callbacks: Vec<ListItemCallbacks>,
first_id: u64,
}
impl QueryListItemStore {
pub fn callback_of_id(&self, id: u64) -> Option<&ListItemCallbacks> {
let offset = id - self.first_id;
self.callbacks.get(
offset
.checked_as::<usize>()
.expect("there should not be way too many callbacks stored (over u32::MAX)"),
)
}
}
/// Unique ID generator by incrementing numbers.
struct AutoIncrementer(AtomicU64);
impl AutoIncrementer {
/// Retrieves several auto-incremented IDs at once.
pub fn fetch_many(&self, count: u64) -> Range<u64> {
let lower_bound = self.0.fetch_add(count, Ordering::Relaxed);
let upper_bound = lower_bound + count;
lower_bound..upper_bound
}
}