Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache already-drawn timeline items to avoid expensive draw flows #46

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ imbl = { version = "2.0.0", features = ["serde"] } # same as matrix-sdk-ui
imghdr = "0.7.0"
matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk", features = [ "experimental-sliding-sync" ] }
matrix-sdk-ui = { git = "https://github.com/matrix-org/matrix-rust-sdk" }
rangemap = "1.5.0"
tokio = { version = "1.33.0", features = ["macros", "rt-multi-thread"] }
tracing-subscriber = "0.3.17"
unicode-segmentation = "1.10.1"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ cargo run -- 'USERNAME' 'PASSWORD' ['HOMESERVER_URL']

3. Build and run Robrix using `cargo-makepad`:
* Fill in your username and password in the [`login.toml`](login.toml) file.
* Then use cargo makepad to build and run:
```sh
cargo makepad android run -p robrix --release
```
Expand Down
Binary file removed resources/img/loading.png
Binary file not shown.
701 changes: 491 additions & 210 deletions src/home/room_screen.rs

Large diffs are not rendered by default.

27 changes: 23 additions & 4 deletions src/media_cache.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::{sync::{Mutex, Arc}, collections::{BTreeMap, btree_map::Entry}, time::SystemTime, ops::{Deref, DerefMut}};
use makepad_widgets::{error, log};
use matrix_sdk::{ruma::{OwnedMxcUri, events::room::MediaSource}, media::{MediaRequest, MediaFormat}};
use crate::{sliding_sync::{self, MatrixRequest}, utils::{MEDIA_THUMBNAIL_FORMAT, MediaFormatConst}};
use crate::{home::room_screen::TimelineUpdate, sliding_sync::{self, MatrixRequest}, utils::{MediaFormatConst, MEDIA_THUMBNAIL_FORMAT}};

pub static AVATAR_CACHE: Mutex<MediaCache> = Mutex::new(MediaCache::new(MEDIA_THUMBNAIL_FORMAT));
pub static AVATAR_CACHE: Mutex<MediaCache> = Mutex::new(MediaCache::new(MEDIA_THUMBNAIL_FORMAT, None));

pub type MediaCacheEntryRef = Arc<Mutex<MediaCacheEntry>>;

Expand All @@ -24,6 +24,8 @@ pub struct MediaCache {
cache: BTreeMap<OwnedMxcUri, MediaCacheEntryRef>,
/// The default format to use when fetching media.
default_format: MediaFormatConst,
/// A channel to send updates to a particular timeline when a media request has completed.
timeline_update_sender: Option<crossbeam_channel::Sender<TimelineUpdate>>,
}
impl Deref for MediaCache {
type Target = BTreeMap<OwnedMxcUri, MediaCacheEntryRef>;
Expand All @@ -40,10 +42,17 @@ impl DerefMut for MediaCache {
impl MediaCache {
/// Creates a new media cache that will use the given media format
/// when fetching media from the server.
pub const fn new(default_format: MediaFormatConst) -> Self {
///
/// It will also optionally send updates to the given timeline update sender
/// when a media request has completed.
pub const fn new(
default_format: MediaFormatConst,
timeline_update_sender: Option<crossbeam_channel::Sender<TimelineUpdate>>,
) -> Self {
Self {
cache: BTreeMap::new(),
default_format,
timeline_update_sender,
}
}

Expand Down Expand Up @@ -83,6 +92,7 @@ impl MediaCache {
},
on_fetched: insert_into_cache,
destination,
update_sender: self.timeline_update_sender.clone(),
}
);
MediaCacheEntry::Requested
Expand All @@ -91,7 +101,12 @@ impl MediaCache {
}

/// Insert data into a previously-requested media cache entry.
fn insert_into_cache(value_ref: &Mutex<MediaCacheEntry>, _request: MediaRequest, data: matrix_sdk::Result<Vec<u8>>) {
fn insert_into_cache(
value_ref: &Mutex<MediaCacheEntry>,
_request: MediaRequest,
data: matrix_sdk::Result<Vec<u8>>,
update_sender: Option<crossbeam_channel::Sender<TimelineUpdate>>,
) {
let new_value = match data {
Ok(data) => {

Expand Down Expand Up @@ -120,4 +135,8 @@ fn insert_into_cache(value_ref: &Mutex<MediaCacheEntry>, _request: MediaRequest,
}
};
*value_ref.lock().unwrap() = new_value;

if let Some(sender) = update_sender {
let _ = sender.send(TimelineUpdate::MediaFetched);
}
}
23 changes: 16 additions & 7 deletions src/sliding_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,13 @@ pub enum MatrixRequest {
},
/// Request to fetch media from the server.
/// Upon completion of the async media request, the `on_fetched` function
/// will be invoked with the `destination` and the result of the media fetch.
/// will be invoked with four arguments: the `destination`, the `media_request`,
/// the result of the media fetch, and the `update_sender`.
FetchMedia {
media_request: MediaRequest,
on_fetched: fn(&Mutex<MediaCacheEntry>, MediaRequest, matrix_sdk::Result<Vec<u8>>),
on_fetched: fn(&Mutex<MediaCacheEntry>, MediaRequest, matrix_sdk::Result<Vec<u8>>, Option<crossbeam_channel::Sender<TimelineUpdate>>),
destination: Arc<Mutex<MediaCacheEntry>>,
update_sender: Option<crossbeam_channel::Sender<TimelineUpdate>>,
},
/// Request to send a message to the given room's timeline.
SendMessage {
Expand Down Expand Up @@ -234,14 +236,14 @@ async fn async_worker(mut receiver: UnboundedReceiver<MatrixRequest>) -> Result<
});
}

MatrixRequest::FetchMedia { media_request, on_fetched, destination } => {
MatrixRequest::FetchMedia { media_request, on_fetched, destination, update_sender } => {
let Some(client) = CLIENT.get() else { continue };
let media = client.media();

let _fetch_task = Handle::current().spawn(async move {
// log!("Sending fetch media request for {media_request:?}...");
let res = media.get_media_content(&media_request, true).await;
on_fetched(&destination, media_request, res);
on_fetched(&destination, media_request, res, update_sender);
});
}

Expand Down Expand Up @@ -370,15 +372,22 @@ static ALL_ROOM_INFO: Mutex<BTreeMap<OwnedRoomId, RoomInfo>> = Mutex::new(BTreeM
/// The logged-in Matrix client, which can be freely and cheaply cloned.
static CLIENT: OnceLock<Client> = OnceLock::new();

/// Returns the timeline update receiver for the given room, if one exists.
/// Returns the timeline update sender and receiver endpoints for the given room,
/// if and only if the receiver exists.
///
/// This will only succeed once per room, as only a single channel receiver can exist.
pub fn take_timeline_update_receiver(
room_id: &OwnedRoomId,
) -> Option<crossbeam_channel::Receiver<TimelineUpdate>> {
) -> Option<(
crossbeam_channel::Sender<TimelineUpdate>,
crossbeam_channel::Receiver<TimelineUpdate>,
)>
{
ALL_ROOM_INFO.lock().unwrap()
.get_mut(room_id)
.and_then(|ri| ri.timeline_update_receiver.take())
.and_then(|ri| ri.timeline_update_receiver.take()
.map(|receiver| (ri.timeline_update_sender.clone(), receiver))
)
}


Expand Down