diff --git a/bindings/matrix-sdk-ffi/src/room_list.rs b/bindings/matrix-sdk-ffi/src/room_list.rs index 8b875173b84..39c5b556f41 100644 --- a/bindings/matrix-sdk-ffi/src/room_list.rs +++ b/bindings/matrix-sdk-ffi/src/room_list.rs @@ -1,26 +1,20 @@ -use std::{fmt::Debug, sync::Arc, time::Duration}; +use std::{fmt::Debug, mem::MaybeUninit, ptr::addr_of_mut, sync::Arc, time::Duration}; use eyeball_im::VectorDiff; use futures_util::{pin_mut, StreamExt, TryFutureExt}; -use matrix_sdk::{ - ruma::{ - api::client::sync::sync_events::{ - v4::RoomSubscription as RumaRoomSubscription, - UnreadNotificationsCount as RumaUnreadNotificationsCount, - }, - assign, RoomId, +use matrix_sdk::ruma::{ + api::client::sync::sync_events::{ + v4::RoomSubscription as RumaRoomSubscription, + UnreadNotificationsCount as RumaUnreadNotificationsCount, }, - RoomListEntry as MatrixRoomListEntry, + assign, RoomId, }; use matrix_sdk_ui::{ - room_list_service::{ - filters::{ - new_filter_all, new_filter_any, new_filter_category, new_filter_favourite, - new_filter_fuzzy_match_room_name, new_filter_invite, new_filter_joined, - new_filter_non_left, new_filter_none, new_filter_normalized_match_room_name, - new_filter_unread, RoomCategory, - }, - BoxedFilterFn, + room_list_service::filters::{ + new_filter_all, new_filter_any, new_filter_category, new_filter_favourite, + new_filter_fuzzy_match_room_name, new_filter_invite, new_filter_joined, + new_filter_non_left, new_filter_none, new_filter_normalized_match_room_name, + new_filter_unread, BoxedFilterFn, RoomCategory, }, timeline::default_event_filter, unable_to_decrypt_hook::UtdHookManager, @@ -84,27 +78,6 @@ impl From for RoomListError { } } -#[derive(uniffi::Record)] -pub struct RoomListRange { - pub start: u32, - pub end_inclusive: u32, -} - -#[derive(uniffi::Enum)] -pub enum RoomListInput { - Viewport { ranges: Vec }, -} - -impl From for matrix_sdk_ui::room_list_service::Input { - fn from(value: RoomListInput) -> Self { - match value { - RoomListInput::Viewport { ranges } => Self::Viewport( - ranges.iter().map(|range| range.start..=range.end_inclusive).collect(), - ), - } - } -} - #[derive(uniffi::Object)] pub struct RoomListService { pub(crate) inner: Arc, @@ -141,10 +114,6 @@ impl RoomListService { })) } - async fn apply_input(&self, input: RoomListInput) -> Result<(), RoomListError> { - self.inner.apply_input(input.into()).await.map(|_| ()).map_err(Into::into) - } - fn sync_indicator( &self, delay_before_showing_in_ms: u32, @@ -192,45 +161,114 @@ impl RoomList { }) } - fn entries(&self, listener: Box) -> RoomListEntriesResult { - let (entries, entries_stream) = self.inner.entries(); + fn entries(&self, listener: Box) -> Arc { + let this = self.inner.clone(); + + Arc::new(TaskHandle::new(RUNTIME.spawn(async move { + let (entries, entries_stream) = this.entries(); - RoomListEntriesResult { - entries: entries.into_iter().map(Into::into).collect(), - entries_stream: Arc::new(TaskHandle::new(RUNTIME.spawn(async move { - pin_mut!(entries_stream); + pin_mut!(entries_stream); - while let Some(diff) = entries_stream.next().await { - listener.on_update(diff.into_iter().map(Into::into).collect()); - } - }))), - } + listener.on_update(vec![RoomListEntriesUpdate::Append { + values: entries.into_iter().map(|v| Arc::new(v.into())).collect(), + }]); + + while let Some(diff) = entries_stream.next().await { + listener.on_update(diff.into_iter().map(Into::into).collect()); + } + }))) } fn entries_with_dynamic_adapters( - &self, + self: Arc, page_size: u32, listener: Box, - ) -> RoomListEntriesWithDynamicAdaptersResult { + ) -> Arc { + let this = self.clone(); + let client = self.room_list_service.inner.client(); + + // The following code deserves a bit of explanation. + // `matrix_sdk_ui::room_list_service::RoomList::entries_with_dynamic_adapters` + // returns a `Stream` with a lifetime bounds to its `self` (`RoomList`). This is + // problematic here as this `Stream` is returned as part of + // `RoomListEntriesWithDynamicAdaptersResult` but it is not possible to store + // `RoomList` with it inside the `Future` that is run inside the `TaskHandle` + // that consumes this `Stream`. We have a lifetime issue: `RoomList` doesn't + // live long enough! + // + // To solve this issue, the trick is to store the `RoomList` inside the + // `RoomListEntriesWithDynamicAdaptersResult`. Alright, but then we have another + // lifetime issue! `RoomList` cannot move inside this struct because it is + // borrowed by `entries_with_dynamic_adapters`. Indeed, the struct is built + // after the `Stream` is obtained. + // + // To solve this issue, we need to build the struct field by field, starting + // with `this`, and use a reference to `this` to call + // `entries_with_dynamic_adapters`. This is unsafe because a couple of + // invariants must hold, but all this is legal and correct if the invariants are + // properly fulfilled. + + // Create the struct result with uninitialized fields. + let mut result = MaybeUninit::::uninit(); + let ptr = result.as_mut_ptr(); + + // Initialize the first field `this`. + // + // SAFETY: `ptr` is correctly aligned, this is guaranteed by `MaybeUninit`. + unsafe { + addr_of_mut!((*ptr).this).write(this); + } + + // Get a reference to `this`. It is only borrowed, it's not moved. + let this = + // SAFETY: `ptr` is correct aligned, the `this` field is correctly aligned, + // is dereferenceable and points to a correctly initialized value as done + // in the previous line. + unsafe { addr_of_mut!((*ptr).this).as_ref() } + // SAFETY: `this` contains a non null value. + .unwrap(); + + // Now we can create `entries_stream` and `dynamic_entries_controller` by + // borrowing `this`, which is going to live long enough since it will live as + // long as `entries_stream` and `dynamic_entries_controller`. let (entries_stream, dynamic_entries_controller) = - self.inner.entries_with_dynamic_adapters( + this.inner.entries_with_dynamic_adapters( page_size.try_into().unwrap(), - self.room_list_service.inner.client().roominfo_update_receiver(), + client.roominfo_update_receiver(), ); - RoomListEntriesWithDynamicAdaptersResult { - controller: Arc::new(RoomListDynamicEntriesController::new( - dynamic_entries_controller, - self.room_list_service.inner.client(), - )), - entries_stream: Arc::new(TaskHandle::new(RUNTIME.spawn(async move { - pin_mut!(entries_stream); + // FFI dance to make those values consumable by foreign language, nothing fancy + // here, that's the real code for this method. + let dynamic_entries_controller = + Arc::new(RoomListDynamicEntriesController::new(dynamic_entries_controller)); - while let Some(diff) = entries_stream.next().await { - listener.on_update(diff.into_iter().map(Into::into).collect()); - } - }))), + let entries_stream = Arc::new(TaskHandle::new(RUNTIME.spawn(async move { + pin_mut!(entries_stream); + + while let Some(diff) = entries_stream.next().await { + listener.on_update(diff.into_iter().map(Into::into).collect()); + } + }))); + + // Initialize the second field `controller`. + // + // SAFETY: `ptr` is correctly aligned. + unsafe { + addr_of_mut!((*ptr).controller).write(dynamic_entries_controller); + } + + // Initialize the third and last field `entries_stream`. + // + // SAFETY: `ptr` is correctly aligned. + unsafe { + addr_of_mut!((*ptr).entries_stream).write(entries_stream); } + + // The result is complete, let's return it! + // + // SAFETY: `result` is fully initialized, all its fields have received a valid + // value. + Arc::new(unsafe { result.assume_init() }) } fn room(&self, room_id: String) -> Result, RoomListError> { @@ -238,14 +276,9 @@ impl RoomList { } } -#[derive(uniffi::Record)] -pub struct RoomListEntriesResult { - pub entries: Vec, - pub entries_stream: Arc, -} - -#[derive(uniffi::Record)] +#[derive(uniffi::Object)] pub struct RoomListEntriesWithDynamicAdaptersResult { + this: Arc, pub controller: Arc, pub entries_stream: Arc, } @@ -334,42 +367,42 @@ pub trait RoomListServiceSyncIndicatorListener: Send + Sync + Debug { #[derive(uniffi::Enum)] pub enum RoomListEntriesUpdate { - Append { values: Vec }, + Append { values: Vec> }, Clear, - PushFront { value: RoomListEntry }, - PushBack { value: RoomListEntry }, + PushFront { value: Arc }, + PushBack { value: Arc }, PopFront, PopBack, - Insert { index: u32, value: RoomListEntry }, - Set { index: u32, value: RoomListEntry }, + Insert { index: u32, value: Arc }, + Set { index: u32, value: Arc }, Remove { index: u32 }, Truncate { length: u32 }, - Reset { values: Vec }, + Reset { values: Vec> }, } -impl From> for RoomListEntriesUpdate { - fn from(other: VectorDiff) -> Self { +impl From> for RoomListEntriesUpdate { + fn from(other: VectorDiff) -> Self { match other { VectorDiff::Append { values } => { - Self::Append { values: values.into_iter().map(Into::into).collect() } + Self::Append { values: values.into_iter().map(|v| Arc::new(v.into())).collect() } } VectorDiff::Clear => Self::Clear, - VectorDiff::PushFront { value } => Self::PushFront { value: value.into() }, - VectorDiff::PushBack { value } => Self::PushBack { value: value.into() }, + VectorDiff::PushFront { value } => Self::PushFront { value: Arc::new(value.into()) }, + VectorDiff::PushBack { value } => Self::PushBack { value: Arc::new(value.into()) }, VectorDiff::PopFront => Self::PopFront, VectorDiff::PopBack => Self::PopBack, VectorDiff::Insert { index, value } => { - Self::Insert { index: u32::try_from(index).unwrap(), value: value.into() } + Self::Insert { index: u32::try_from(index).unwrap(), value: Arc::new(value.into()) } } VectorDiff::Set { index, value } => { - Self::Set { index: u32::try_from(index).unwrap(), value: value.into() } + Self::Set { index: u32::try_from(index).unwrap(), value: Arc::new(value.into()) } } VectorDiff::Remove { index } => Self::Remove { index: u32::try_from(index).unwrap() }, VectorDiff::Truncate { length } => { Self::Truncate { length: u32::try_from(length).unwrap() } } VectorDiff::Reset { values } => { - Self::Reset { values: values.into_iter().map(Into::into).collect() } + Self::Reset { values: values.into_iter().map(|v| Arc::new(v.into())).collect() } } } } @@ -383,23 +416,20 @@ pub trait RoomListEntriesListener: Send + Sync + Debug { #[derive(uniffi::Object)] pub struct RoomListDynamicEntriesController { inner: matrix_sdk_ui::room_list_service::RoomListDynamicEntriesController, - client: matrix_sdk::Client, } impl RoomListDynamicEntriesController { fn new( dynamic_entries_controller: matrix_sdk_ui::room_list_service::RoomListDynamicEntriesController, - client: &matrix_sdk::Client, ) -> Self { - Self { inner: dynamic_entries_controller, client: client.clone() } + Self { inner: dynamic_entries_controller } } } #[uniffi::export] impl RoomListDynamicEntriesController { fn set_filter(&self, kind: RoomListEntriesDynamicFilterKind) -> bool { - let FilterWrapper(filter) = FilterWrapper::from(&self.client, kind); - self.inner.set_filter(filter) + self.inner.set_filter(kind.into()) } fn add_one_page(&self) { @@ -441,33 +471,29 @@ impl From for RoomCategory { } } -/// Custom internal type to transform a `RoomListEntriesDynamicFilterKind` into -/// a `BoxedFilterFn`. -struct FilterWrapper(BoxedFilterFn); - -impl FilterWrapper { - fn from(client: &matrix_sdk::Client, value: RoomListEntriesDynamicFilterKind) -> Self { +impl From for BoxedFilterFn { + fn from(value: RoomListEntriesDynamicFilterKind) -> Self { use RoomListEntriesDynamicFilterKind as Kind; match value { - Kind::All { filters } => Self(Box::new(new_filter_all( - filters.into_iter().map(|filter| FilterWrapper::from(client, filter).0).collect(), - ))), - Kind::Any { filters } => Self(Box::new(new_filter_any( - filters.into_iter().map(|filter| FilterWrapper::from(client, filter).0).collect(), - ))), - Kind::NonLeft => Self(Box::new(new_filter_non_left(client))), - Kind::Joined => Self(Box::new(new_filter_joined(client))), - Kind::Unread => Self(Box::new(new_filter_unread(client))), - Kind::Favourite => Self(Box::new(new_filter_favourite(client))), - Kind::Invite => Self(Box::new(new_filter_invite(client))), - Kind::Category { expect } => Self(Box::new(new_filter_category(client, expect.into()))), - Kind::None => Self(Box::new(new_filter_none())), + Kind::All { filters } => Box::new(new_filter_all( + filters.into_iter().map(|filter| BoxedFilterFn::from(filter)).collect(), + )), + Kind::Any { filters } => Box::new(new_filter_any( + filters.into_iter().map(|filter| BoxedFilterFn::from(filter)).collect(), + )), + Kind::NonLeft => Box::new(new_filter_non_left()), + Kind::Joined => Box::new(new_filter_joined()), + Kind::Unread => Box::new(new_filter_unread()), + Kind::Favourite => Box::new(new_filter_favourite()), + Kind::Invite => Box::new(new_filter_invite()), + Kind::Category { expect } => Box::new(new_filter_category(expect.into())), + Kind::None => Box::new(new_filter_none()), Kind::NormalizedMatchRoomName { pattern } => { - Self(Box::new(new_filter_normalized_match_room_name(client, &pattern))) + Box::new(new_filter_normalized_match_room_name(&pattern)) } Kind::FuzzyMatchRoomName { pattern } => { - Self(Box::new(new_filter_fuzzy_match_room_name(client, &pattern))) + Box::new(new_filter_fuzzy_match_room_name(&pattern)) } } } @@ -479,6 +505,12 @@ pub struct RoomListItem { utd_hook: Option>, } +impl From for RoomListItem { + fn from(value: matrix_sdk_ui::room_list_service::Room) -> Self { + Self { inner: Arc::new(value), utd_hook: None } + } +} + #[uniffi::export(async_runtime = "tokio")] impl RoomListItem { fn id(&self) -> String { @@ -504,7 +536,7 @@ impl RoomListItem { self.inner.inner_room().canonical_alias().map(|alias| alias.to_string()) } - pub async fn room_info(&self) -> Result { + async fn room_info(&self) -> Result { Ok(RoomInfo::new(self.inner.inner_room()).await?) } @@ -579,31 +611,6 @@ impl RoomListItem { } } -#[derive(Clone, Debug, uniffi::Enum)] -pub enum RoomListEntry { - Empty, - Invalidated { room_id: String }, - Filled { room_id: String }, -} - -impl From for RoomListEntry { - fn from(value: MatrixRoomListEntry) -> Self { - (&value).into() - } -} - -impl From<&MatrixRoomListEntry> for RoomListEntry { - fn from(value: &MatrixRoomListEntry) -> Self { - match value { - MatrixRoomListEntry::Empty => Self::Empty, - MatrixRoomListEntry::Filled(room_id) => Self::Filled { room_id: room_id.to_string() }, - MatrixRoomListEntry::Invalidated(room_id) => { - Self::Invalidated { room_id: room_id.to_string() } - } - } - } -} - #[derive(uniffi::Record)] pub struct RequiredState { pub key: String,