-
Notifications
You must be signed in to change notification settings - Fork 34
Fix @mention search performance and refactor search functionality #550
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
base: main
Are you sure you want to change the base?
Fix @mention search performance and refactor search functionality #550
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks Alex, the code looks quite good in general!
I have only one major suggestion, which addresses the problem that there is no way to cancel a member search that's ongoing in the background.
Instead of using Makepad actions to send the results from the background task to the UI thread (which requires a search_id
), we should just use a direct channel to communicate between them. This design has a few benefits:
- No need for a
search_id
: just create the channel in the MentionableTextInput code and include thesender
side of the channel in theMatrixRequest:: SearchRoomMembers
request.- The MentionableTextInput will hold on to the
receiver
side of the channel and check it for updates. You could certainly use a dedicated action to tell the MentionableTextInput widget to check its channel for updates; that would be better than usingEvent::Signal
like we typically do.
- The MentionableTextInput will hold on to the
- This allows a
SearchRoomMembers
request to be aborted/canceled very easily. We only have to drop thereceiver
side in the MentionableTextInput, which will then cause anysender.send()
operation in the background thread to fail.- When
send()
fails, we can stop searching and return from thesearch_room_members_streaming
function, as that would indicate that the UI side is no longer listening for updates.
- When
- Reduces the quantity and the size of actions that are sent through Makepad's event system, which itself reduces the amount of memory allocations needed to store those larger action types.
…ng/robrix into optimize-member-search
Further optimizations have been made:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have created a PR request after some amendments.
src/shared/mentionable_text_input.rs
Outdated
submit_async_request(MatrixRequest::SearchRoomMembers { | ||
room_id: room_props.room_id.clone(), | ||
search_text: search_text.to_string(), | ||
sender, | ||
max_results: max_visible_items * SEARCH_BUFFER_MULTIPLIER, | ||
cached_members: cached_members.clone(), | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use cx.spawn, instead of using submit_async_request. Also there are two such calls duplicated in check_search_channel, user_update_list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That’s strange—it seems like you’re looking at the old code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why use cx.spawn
? Does this method exist?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"cx.spawn" seems to be changed to cx.spawn_thread.
- There is no matrix Request to be made for submit_async_request(MatrixRequest::SearchRoomMembers).
- It is also not using room's timelineUpdate sender.
- "search_room_members_streaming" is not an async function anyway. Hence there is no need for tokio.
- It is a just a thread that does data processing. " cx.spawn_thread" should work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. you're right. Fixed it.
However, directly using cx.spawn_local
here is not the best solution; relying on tokio
is necessary.
Directly using cx.spawn_local
is not the optimal approach in this context. It’s still necessary to rely on tokio, because tokio
manages spawn_blocking
threads and task cancellation more effectively than Makepad’s built-in threading. Of course, I won’t continue using submit_async_request(MatrixRequest::SearchRoomMembers)
here, to avoid introducing logical ambiguity.
The code in your PR seems to be outdated.None of them are based on my latest code.The issue you mentioned does not exist in the current code. |
May I ask what issue you encountered during testing? Please comment here directly instead of submitting a PR, as the code will conflict. @alanpoon |
src/shared/mentionable_text_input.rs
Outdated
submit_async_request(MatrixRequest::SearchRoomMembers { | ||
room_id: room_props.room_id.clone(), | ||
search_text: search_text.to_string(), | ||
sender, | ||
max_results: max_visible_items * SEARCH_BUFFER_MULTIPLIER, | ||
cached_members: cached_members.clone(), | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"cx.spawn" seems to be changed to cx.spawn_thread.
- There is no matrix Request to be made for submit_async_request(MatrixRequest::SearchRoomMembers).
- It is also not using room's timelineUpdate sender.
- "search_room_members_streaming" is not an async function anyway. Hence there is no need for tokio.
- It is a just a thread that does data processing. " cx.spawn_thread" should work.
Further optimizations:
|
Given that the Alvin's logout PR will do a Tokio::runtime::shutdown_background. So if there is a possible panic when there is any tokio thread running, searching for member and when the user logouts at the same time. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please check with Kevin on Element before making big changes to submit_async_request.
if self.is_searching { | ||
let search_text = self.cmd_text_input.search_text(); | ||
self.update_user_list(cx, &search_text, scope); | ||
} | ||
} else if self.cmd_text_input.view(id!(popup)).visible() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Should handle the actions that hide or show the popup instead of checking the visibility of the popup on any actions.
- Need to CommandTextInput's public functions, rather than querying its inner widgets. Open a discussion on why the existing CommandTextInput's public functions are insufficient.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, the CommandTextInput component doesn’t have any relevant public methods. I can add a TODO comment
here, but I don’t want this PR to be blocked by Makepad
.
src/shared/mentionable_text_input.rs
Outdated
if should_update_ui && !self.accumulated_results.is_empty() { | ||
// Received search results, updating UI... | ||
// Hide loading and show results immediately | ||
self.members_loading = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is weird to set self.members_loading to be false, based on the non-empty accumulated_results. self.show_loading_indicator(cx), never seems to be executed. I would imagine comparing room_props.room_members.len with room.active_members_count() to determine self.members_loading to be true / false.
src/shared/mentionable_text_input.rs
Outdated
if self.accumulated_results.is_empty() { | ||
// Clear results before showing empty state | ||
self.accumulated_results.clear(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you want to clear an empty array?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, it looks like some redundant code wasn’t completely removed.
self.cmd_text_input.text_input_ref().set_key_focus(cx); | ||
if self.members_loading { | ||
// Show loading indicator while fetching members | ||
self.show_loading_indicator(cx); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"self.members_loading" seems to be always false. self.show_loading_indicator(cx), does not seem to be executed.
src/room/member_search.rs
Outdated
let mut all_results = Vec::new(); | ||
let mut sent_count = 0; | ||
|
||
for (index, member) in members.iter().enumerate() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"members" is Arc<Vec>. This means that length of room member list is dynamic, mutated by matrix sdk. This function search_room_members_streaming
is called once. Which means that the mentionable user list only contain the room members existed at that point of time.
I am expecting a while loop with perhaps a time interval / room member sync event to access the Arc<Vec> until the accumulated length == room.active_member_count. Call CommandTextInput.add_item when room members are ready and fit the search criteria.
Also use slice member[i..j]
instead of enumerate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current approach uses a member cache fetched from the Matrix server during room screen initialization, which is sufficient for most use cases and avoids the complexity of real-time synchronization. This PR focuses on delivering MVP functionality; further optimizations can be addressed later to avoid premature optimization. For now, when a user reopens a room, the latest member list is fetched from the server.
src/shared/mentionable_text_input.rs
Outdated
popup.set_visible(cx, true); | ||
self.cmd_text_input.text_input_ref().set_key_focus(cx); | ||
} else { | ||
// Show "no matches" if we're searching and not loading | ||
self.show_no_matches_indicator(cx); | ||
popup.set_visible(cx, true); | ||
self.cmd_text_input.text_input_ref().set_key_focus(cx); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use CommandTextInput's public functions instead of manually accessing child widgets.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let’s leave it for a future PR—I’ve already added a TODO.
src/shared/mentionable_text_input.rs
Outdated
self.search_receiver = None; | ||
|
||
// If we still have loading indicator, clear it | ||
if self.members_loading { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confusing checks before state reset. Move the following code to separate functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored.
src/shared/mentionable_text_input.rs
Outdated
// Clone the indices to avoid borrowing issues | ||
let member_indices = self.accumulated_results.clone(); | ||
let user_items_added = self.add_user_mention_items(cx, &member_indices, user_items_limit, is_desktop, room_props); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just access self.accumulated_results inside add_user_mention_items
? It is not multiple threaded. It is also not Arc<Mutex<Vec>>, where you need lock to avoid data race. If that is what you mean by borrowing issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because there was a compilation error before.This section of the code has now been refactored.
self.accumulated_results.clear(); | ||
|
||
// Check if we have cached members | ||
let has_members = matches!(&room_props.room_members, Some(members) if !members.is_empty()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, has_members may not be enough, room_members is a Arc<Vec>. May not be fully synced at this point of time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refer to the previous explanation.
src/shared/mentionable_text_input.rs
Outdated
self.members_loading = false; | ||
|
||
let popup = self.cmd_text_input.view(id!(popup)); | ||
let header_view = self.cmd_text_input.view(id!(popup.header_view)); | ||
header_view.set_visible(cx, true); | ||
popup.set_visible(cx, true); | ||
self.cmd_text_input.text_input_ref().set_key_focus(cx); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use CommandTextInput's public functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refer to the previous explanation.
This isn’t a major change—it just reuses |
I don't disagree with the name change, but let's do it in a separate PR. There are various considerations here, and I don't think there's much of a compelling reason to differentiate between Matrix vs. non-Matrix background requests, especially if they're handled by the same worker. For now, you can just move the In my TSP support PR (#557), I've created a completely separate worker handler and request sender channel for this purpose, so it's probably best for us to consolidate these later, such that we have one Matrix-specific async worker, and one general async worker for everything else. But doing it in a separate PR will make everything easier to review — there's no need for 1500 lines of unrelated minor changes to |
Fixed issue #452.
The @mention search feature was iterating over the entire members list on the main UI thread, causing significant performance issues and UI freezes, especially in rooms with many members.
Changes
Performance Improvements