Commit d5d8097c3afd
tarball9 files changed+113-42
@@ -1827,6 +1827,7 @@ dependencies = [
18271827
"getrandom 0.3.4",18281828
"getrandom 0.4.3",18291829
"http",1830
+ "imbl",18301831
"matrix-sdk",18311832
"matrix-sdk-crypto",18321833
"matrix-sdk-test",@@ -6,7 +6,7 @@ description = "A compact application backend on top of the Matrix Rust SDK"
66
77
[features]88
default = []9
-serde = ["dep:serde"]9
+serde = ["dep:serde", "imbl/serde"]1010
sqlite = ["matrix-sdk/sqlite", "serde", "dep:url"]1111
bundled-sqlite = ["sqlite", "matrix-sdk/bundled-sqlite"]1212
indexeddb = ["matrix-sdk/indexeddb", "serde", "dep:url"]@@ -14,6 +14,7 @@ indexeddb = ["matrix-sdk/indexeddb", "serde", "dep:url"]
1414
[dependencies]1515
futures-util = { version = "0.3.31", default-features = false, features = ["std"] }1616
http = "1"17
+imbl = { version = "6.1.0", default-features = false }1718
matrix-sdk = { version = "0.18.0", default-features = false, features = ["e2e-encryption"] }1819
matrix-sdk-crypto = { version = "0.18.0", default-features = false }1920
matrix-sdk-ui = { version = "0.18.0", default-features = false }@@ -29,9 +29,10 @@ drop. a sent message answers when the sdk send queue has it, delivery shows up
2929
as the timeline item's send state. reads like `rooms`, `members` and `media`3030
go straight to the sdk. the stream only carries state3131
32
-start a timeline from `TimelineReset`, then apply `TimelineChanged` diffs with33
-`TimelineDiff::try_apply`. when one doesn't fit, stop applying, call `resync`,34
-and pick up from the next `TimelineReset`32
+start a timeline from `TimelineReset`, then apply each `TimelineChanged` whole33
+with `TimelineDiff::try_apply_all`. snapshots are `imbl::Vector`s, re-exported34
+as `macltok::imbl`, so a copy costs nothing. when a batch doesn't fit none of it35
+lands. stop applying, call `resync`, and pick up from the next `TimelineReset`3536
3637
With `sqlite` or `indexeddb`, persist the initial `Session::record()`3738
separately from its encrypted store. On `SessionTokensChanged`, obtain a new record from@@ -84,9 +84,12 @@ and gets logged
8484
## timelines8585
8686
`TimelineDiff::try_apply` refuses a diff whose indices don't fit your snapshot87
-and leaves the snapshot alone. stop applying, `resync`, and carry on from the88
-next `TimelineReset`. resync kills the old watcher before the new reset goes89
-out, so no stale diff can land after it87
+and leaves the snapshot alone. `try_apply_all` does the same for a whole batch,88
+checking every diff against lengths alone first, since that's all a diff's fit89
+depends on. snapshots are `imbl::Vector`s so keeping one is a pointer copy.90
+stop applying, `resync`, and carry on from the next `TimelineReset`. resync91
+kills the old watcher before the new reset goes out, so no stale diff can land92
+after it9093
9194
`TimelineId` tells a room from a thread by its root event9295
@@ -1,6 +1,6 @@
11
use macltok::{22
EngineConfig, Event, PasswordLogin, Retain, RoomMembership, Session, StoreConfig,3
- TimelineContent, TimelineId, TimelineItem,3
+ TimelineContent, TimelineDiff, TimelineId, TimelineItem, imbl::Vector,44
};55
66
#[tokio::main]@@ -30,7 +30,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
3030
let (engine, handle, mut events) = session.build(EngineConfig::default()).await?;3131
let running = tokio::spawn(engine.run());3232
let mut opened: Option<TimelineId> = None;33
- let mut items = Vec::new();33
+ let mut items = Vector::new();3434
let mut stale = false;3535
3636
loop {@@ -66,7 +66,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
6666
Event::TimelineChanged { timeline_id, diffs }6767
if !stale && opened.as_ref() == Some(&timeline_id) =>6868
{69
- if diffs.into_iter().any(|diff| diff.try_apply(&mut items).is_err()) {69
+ if TimelineDiff::try_apply_all(diffs, &mut items).is_err() {7070
stale = true;7171
let handle = handle.clone();7272
tokio::spawn(async move { handle.resync(timeline_id).await });@@ -96,7 +96,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
9696
Ok(())9797
}9898
99
-fn print_messages(items: &[TimelineItem]) {99
+fn print_messages(items: &Vector<TimelineItem>) {100100
for item in items {101101
if let TimelineItem::Event(event) = item102102
&& let TimelineContent::Message(message) = &event.content@@ -25,6 +25,7 @@ pub use account::{
2525
2626
pub use engine::{Engine, EngineConfig, EngineError};2727
pub use error::{Error, ErrorKind};28
+pub use imbl;2829
pub use model::{2930
AccountCommand, AttachmentUpload, Change, Command, CommandOutput, ConnectionState,3031
CreateRoomOptions, EncryptionCommand, EncryptionState, EngineErrorKind, EngineFailure, Event,@@ -1,5 +1,6 @@
11
use std::{fmt, sync::Arc};22
3
+use imbl::Vector;34
use ruma::{45
OwnedDeviceId, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedRoomOrAliasId,56
OwnedServerName, OwnedTransactionId, OwnedUserId,@@ -648,7 +649,7 @@ pub enum Event {
648649
/// replaces whatever you had for this timeline. every diff after it applies to it649650
TimelineReset {650651
timeline_id: TimelineId,651
- items: Vec<TimelineItem>,652
+ items: Vector<TimelineItem>,652653
},653654
TimelineChanged {654655
timeline_id: TimelineId,@@ -1151,43 +1152,75 @@ impl TimelineDiff {
11511152
/// Apply a diff only when its indices match the current snapshot.11521153
/// On error the snapshot is unchanged; request `TimelineCommand::Resync`11531154
/// and ignore further diffs until the corresponding `TimelineReset`.1154
- pub fn try_apply(self, items: &mut Vec<TimelineItem>) -> Result<(), TimelineDiffError> {1155
+ pub fn try_apply(self, items: &mut Vector<TimelineItem>) -> Result<(), TimelineDiffError> {11551156
let length = items.len();1157
+ if self.length_after(length).is_none() {1158
+ return Err(TimelineDiffError { length });1159
+ }1160
+ self.apply(items);1161
+ Ok(())1162
+ }1163
+1164
+ /// a whole `TimelineChanged` or none of it. whether a diff fits depends only on the1165
+ /// length before it, so the batch is checked against lengths alone before anything1166
+ /// moves. the error carries the length the snapshot would have had at the misfit1167
+ pub fn try_apply_all(1168
+ diffs: Vec<Self>,1169
+ items: &mut Vector<TimelineItem>,1170
+ ) -> Result<(), TimelineDiffError> {1171
+ diffs.iter().try_fold(items.len(), |length, diff| {1172
+ diff.length_after(length)1173
+ .ok_or(TimelineDiffError { length })1174
+ })?;1175
+ for diff in diffs {1176
+ diff.apply(items);1177
+ }1178
+ Ok(())1179
+ }1180
+1181
+ fn length_after(&self, length: usize) -> Option<usize> {1182
+ match self {1183
+ Self::Append { values } => Some(length + values.len()),1184
+ Self::Clear => Some(0),1185
+ Self::PushFront { .. } | Self::PushBack { .. } => Some(length + 1),1186
+ Self::PopFront | Self::PopBack => length.checked_sub(1),1187
+ Self::Insert { index, .. } => (*index <= length).then_some(length + 1),1188
+ Self::Set { index, .. } => (*index < length).then_some(length),1189
+ Self::Remove { index } => (*index < length).then(|| length - 1),1190
+ Self::Truncate { length: target } => (*target <= length).then_some(*target),1191
+ Self::Reset { values } => Some(values.len()),1192
+ }1193
+ }1194
+1195
+ fn apply(self, items: &mut Vector<TimelineItem>) {11561196
match self {11571197
Self::Append { values } => items.extend(values),11581198
Self::Clear => items.clear(),1159
- Self::PushFront { value } => items.insert(0, value),1160
- Self::PushBack { value } => items.push(value),1161
- Self::PopFront if length == 0 => return Err(TimelineDiffError { length }),1199
+ Self::PushFront { value } => items.push_front(value),1200
+ Self::PushBack { value } => items.push_back(value),11621201
Self::PopFront => {1163
- items.remove(0);1202
+ items.pop_front();11641203
}1165
- Self::PopBack if length == 0 => return Err(TimelineDiffError { length }),11661204
Self::PopBack => {1167
- items.pop();1168
- }1169
- Self::Insert { index, .. } if index > length => {1170
- return Err(TimelineDiffError { length });1205
+ items.pop_back();11711206
}11721207
Self::Insert { index, value } => items.insert(index, value),1173
- Self::Set { index, .. } if index >= length => return Err(TimelineDiffError { length }),1174
- Self::Set { index, value } => items[index] = value,1175
- Self::Remove { index } if index >= length => return Err(TimelineDiffError { length }),1208
+ Self::Set { index, value } => {1209
+ items.set(index, value);1210
+ }11761211
Self::Remove { index } => {11771212
items.remove(index);11781213
}1179
- Self::Truncate { length: target } if target > length => {1180
- return Err(TimelineDiffError { length });1181
- }11821214
Self::Truncate { length } => items.truncate(length),1183
- Self::Reset { values } => *items = values,1215
+ Self::Reset { values } => *items = values.into_iter().collect(),11841216
}1185
- Ok(())11861217
}11871218
}11881219
11891220
#[cfg(test)]11901221
mod tests {1222
+ use imbl::{Vector, vector};1223
+11911224
use super::{TimelineDiff, TimelineItem};11921225
11931226
fn item(timestamp_ms: u64) -> TimelineItem {@@ -1196,14 +1229,14 @@ mod tests {
11961229
11971230
#[test]11981231
fn timeline_diff_try_apply_handles_every_variant() {1199
- let mut items = vec![item(1), item(2)];1232
+ let mut items = vector![item(1), item(2)];12001233
12011234
TimelineDiff::Append {12021235
values: vec![item(3)],12031236
}12041237
.try_apply(&mut items)12051238
.unwrap();1206
- assert_eq!(items, vec![item(1), item(2), item(3)]);1239
+ assert_eq!(items, vector![item(1), item(2), item(3)]);12071240
12081241
TimelineDiff::Clear.try_apply(&mut items).unwrap();12091242
assert!(items.is_empty());@@ -1214,7 +1247,7 @@ mod tests {
12141247
TimelineDiff::PushBack { value: item(3) }12151248
.try_apply(&mut items)12161249
.unwrap();1217
- assert_eq!(items, vec![item(2), item(3)]);1250
+ assert_eq!(items, vector![item(2), item(3)]);12181251
12191252
TimelineDiff::PopFront.try_apply(&mut items).unwrap();12201253
TimelineDiff::PopBack.try_apply(&mut items).unwrap();@@ -1231,7 +1264,7 @@ mod tests {
12311264
}12321265
.try_apply(&mut items)12331266
.unwrap();1234
- assert_eq!(items, vec![item(1), item(2), item(3)]);1267
+ assert_eq!(items, vector![item(1), item(2), item(3)]);12351268
12361269
TimelineDiff::Set {12371270
index: 1,@@ -1239,17 +1272,17 @@ mod tests {
12391272
}12401273
.try_apply(&mut items)12411274
.unwrap();1242
- assert_eq!(items, vec![item(1), item(4), item(3)]);1275
+ assert_eq!(items, vector![item(1), item(4), item(3)]);12431276
12441277
TimelineDiff::Remove { index: 1 }12451278
.try_apply(&mut items)12461279
.unwrap();1247
- assert_eq!(items, vec![item(1), item(3)]);1280
+ assert_eq!(items, vector![item(1), item(3)]);12481281
12491282
TimelineDiff::Truncate { length: 1 }12501283
.try_apply(&mut items)12511284
.unwrap();1252
- assert_eq!(items, vec![item(1)]);1285
+ assert_eq!(items, vector![item(1)]);12531286
}12541287
12551288
#[test]@@ -1267,16 +1300,47 @@ mod tests {
12671300
TimelineDiff::Truncate { length: 3 },12681301
];12691302
for diff in invalid {1270
- let mut items = vec![item(1), item(2)];1303
+ let mut items = vector![item(1), item(2)];12711304
assert_eq!(diff.try_apply(&mut items).unwrap_err().length, 2);1272
- assert_eq!(items, vec![item(1), item(2)]);1305
+ assert_eq!(items, vector![item(1), item(2)]);12731306
}12741307
for diff in [TimelineDiff::PopFront, TimelineDiff::PopBack] {1275
- let mut items = Vec::new();1308
+ let mut items = Vector::new();12761309
assert_eq!(diff.try_apply(&mut items).unwrap_err().length, 0);12771310
assert!(items.is_empty());12781311
}12791312
}1313
+1314
+ #[test]1315
+ fn a_batch_that_goes_wrong_halfway_changes_nothing() {1316
+ let mut items = vector![item(1), item(2)];1317
+ let batch = vec![1318
+ TimelineDiff::PushBack { value: item(3) },1319
+ TimelineDiff::Remove { index: 0 },1320
+ TimelineDiff::Set {1321
+ index: 2,1322
+ value: item(4),1323
+ },1324
+ ];1325
+ assert_eq!(1326
+ TimelineDiff::try_apply_all(batch, &mut items)1327
+ .unwrap_err()1328
+ .length,1329
+ 21330
+ );1331
+ assert_eq!(items, vector![item(1), item(2)]);1332
+1333
+ let batch = vec![1334
+ TimelineDiff::PushBack { value: item(3) },1335
+ TimelineDiff::Remove { index: 0 },1336
+ TimelineDiff::Set {1337
+ index: 1,1338
+ value: item(4),1339
+ },1340
+ ];1341
+ TimelineDiff::try_apply_all(batch, &mut items).unwrap();1342
+ assert_eq!(items, vector![item(2), item(4)]);1343
+ }12801344
}12811345
12821346
#[derive(Clone, Debug, Eq, Hash, PartialEq)]@@ -31,7 +31,7 @@ use super::media::{
3131
3232
#[derive(Default)]3333
pub(crate) struct NormalisedTimeline {34
- pub(crate) items: Vec<TimelineItem>,34
+ pub(crate) items: imbl::Vector<TimelineItem>,3535
pub(crate) media: Vec<MediaRegistration>,3636
/// replies whose target isn't loaded, so somebody should go and ask for it3737
pub(crate) missing_replies: Vec<OwnedEventId>,@@ -518,7 +518,7 @@ async fn resync_and_confirm_reset_first(
518518
handle: &ClientHandle,519519
events: &mut EventStream,520520
timeline_id: TimelineId,521
-) -> Vec<TimelineItem> {521
+) -> macltok::imbl::Vector<TimelineItem> {522522
let mut pending = handle523523
.send(Command::Timeline(TimelineCommand::Resync {524524
timeline_id: timeline_id.clone(),