Follower simplification (#8026)
Release Notes: - Improved reliability of following
This commit is contained in:
parent
db0eaca2e5
commit
b14d576349
3 changed files with 143 additions and 76 deletions
|
@ -358,7 +358,6 @@ impl Global for GlobalAppState {}
|
|||
|
||||
pub struct WorkspaceStore {
|
||||
workspaces: HashSet<WindowHandle<Workspace>>,
|
||||
followers: Vec<Follower>,
|
||||
client: Arc<Client>,
|
||||
_subscriptions: Vec<client::Subscription>,
|
||||
}
|
||||
|
@ -2509,6 +2508,10 @@ impl Workspace {
|
|||
};
|
||||
Ok::<_, anyhow::Error>(())
|
||||
})??;
|
||||
if let Some(view) = response.active_view {
|
||||
Self::add_view_from_leader(this.clone(), leader_id, pane.clone(), &view, &mut cx)
|
||||
.await?;
|
||||
}
|
||||
Self::add_views_from_leader(
|
||||
this.clone(),
|
||||
leader_id,
|
||||
|
@ -2726,6 +2729,23 @@ impl Workspace {
|
|||
|
||||
// RPC handlers
|
||||
|
||||
fn active_view_for_follower(&self, cx: &mut ViewContext<Self>) -> Option<proto::View> {
|
||||
let item = self.active_item(cx)?;
|
||||
let leader_id = self
|
||||
.pane_for(&*item)
|
||||
.and_then(|pane| self.leader_for_pane(&pane));
|
||||
|
||||
let item_handle = item.to_followable_item_handle(cx)?;
|
||||
let id = item_handle.remote_id(&self.app_state.client, cx)?;
|
||||
let variant = item_handle.to_state_proto(cx)?;
|
||||
|
||||
Some(proto::View {
|
||||
id: Some(id.to_proto()),
|
||||
leader_id,
|
||||
variant: Some(variant),
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_follow(
|
||||
&mut self,
|
||||
follower_project_id: Option<u64>,
|
||||
|
@ -2734,17 +2754,14 @@ impl Workspace {
|
|||
let client = &self.app_state.client;
|
||||
let project_id = self.project.read(cx).remote_id();
|
||||
|
||||
let active_view_id = self.active_item(cx).and_then(|i| {
|
||||
Some(
|
||||
i.to_followable_item_handle(cx)?
|
||||
.remote_id(client, cx)?
|
||||
.to_proto(),
|
||||
)
|
||||
});
|
||||
let active_view = self.active_view_for_follower(cx);
|
||||
let active_view_id = active_view.as_ref().and_then(|view| view.id.clone());
|
||||
|
||||
cx.notify();
|
||||
|
||||
proto::FollowResponse {
|
||||
active_view,
|
||||
// TODO: once v0.124.0 is retired we can stop sending these
|
||||
active_view_id,
|
||||
views: self
|
||||
.panes()
|
||||
|
@ -2802,19 +2819,35 @@ impl Workspace {
|
|||
) -> Result<()> {
|
||||
match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
|
||||
proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
|
||||
this.update(cx, |this, _| {
|
||||
for (_, state) in &mut this.follower_states {
|
||||
if state.leader_id == leader_id {
|
||||
state.active_view_id =
|
||||
if let Some(active_view_id) = update_active_view.id.clone() {
|
||||
Some(ViewId::from_proto(active_view_id)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let panes_missing_view = this.update(cx, |this, _| {
|
||||
let mut panes = vec![];
|
||||
for (pane, state) in &mut this.follower_states {
|
||||
if state.leader_id != leader_id {
|
||||
continue;
|
||||
}
|
||||
|
||||
state.active_view_id =
|
||||
if let Some(active_view_id) = update_active_view.id.clone() {
|
||||
Some(ViewId::from_proto(active_view_id)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if state.active_view_id.is_some_and(|view_id| {
|
||||
!state.items_by_leader_view_id.contains_key(&view_id)
|
||||
}) {
|
||||
panes.push(pane.clone())
|
||||
}
|
||||
}
|
||||
anyhow::Ok(())
|
||||
anyhow::Ok(panes)
|
||||
})??;
|
||||
|
||||
if let Some(view) = update_active_view.view {
|
||||
for pane in panes_missing_view {
|
||||
Self::add_view_from_leader(this.clone(), leader_id, pane.clone(), &view, cx)
|
||||
.await?
|
||||
}
|
||||
}
|
||||
}
|
||||
proto::update_followers::Variant::UpdateView(update_view) => {
|
||||
let variant = update_view
|
||||
|
@ -2853,6 +2886,56 @@ impl Workspace {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn add_view_from_leader(
|
||||
this: WeakView<Self>,
|
||||
leader_id: PeerId,
|
||||
pane: View<Pane>,
|
||||
view: &proto::View,
|
||||
cx: &mut AsyncWindowContext,
|
||||
) -> Result<()> {
|
||||
let this = this.upgrade().context("workspace dropped")?;
|
||||
|
||||
let item_builders = cx.update(|cx| {
|
||||
cx.default_global::<FollowableItemBuilders>()
|
||||
.values()
|
||||
.map(|b| b.0)
|
||||
.collect::<Vec<_>>()
|
||||
})?;
|
||||
|
||||
let Some(id) = view.id.clone() else {
|
||||
return Err(anyhow!("no id for view")).into();
|
||||
};
|
||||
let id = ViewId::from_proto(id)?;
|
||||
|
||||
let mut variant = view.variant.clone();
|
||||
if variant.is_none() {
|
||||
Err(anyhow!("missing view variant"))?;
|
||||
}
|
||||
|
||||
let task = item_builders.iter().find_map(|build_item| {
|
||||
cx.update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx))
|
||||
.log_err()
|
||||
.flatten()
|
||||
});
|
||||
let Some(task) = task else {
|
||||
return Err(anyhow!(
|
||||
"failed to construct view from leader (maybe from a different version of zed?)"
|
||||
));
|
||||
};
|
||||
|
||||
let item = task.await?;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
let state = this.follower_states.get_mut(&pane)?;
|
||||
item.set_leader_peer_id(Some(leader_id), cx);
|
||||
state.items_by_leader_view_id.insert(id, item);
|
||||
|
||||
Some(())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn add_views_from_leader(
|
||||
this: WeakView<Self>,
|
||||
leader_id: PeerId,
|
||||
|
@ -2920,13 +3003,31 @@ impl Workspace {
|
|||
if cx.is_window_active() {
|
||||
if let Some(item) = self.active_item(cx) {
|
||||
if item.focus_handle(cx).contains_focused(cx) {
|
||||
let leader_id = self
|
||||
.pane_for(&*item)
|
||||
.and_then(|pane| self.leader_for_pane(&pane));
|
||||
|
||||
if let Some(item) = item.to_followable_item_handle(cx) {
|
||||
is_project_item = item.is_project_item(cx);
|
||||
update = proto::UpdateActiveView {
|
||||
id: item
|
||||
.remote_id(&self.app_state.client, cx)
|
||||
.map(|id| id.to_proto()),
|
||||
leader_id: self.leader_for_pane(&self.active_pane),
|
||||
let id = item
|
||||
.remote_id(&self.app_state.client, cx)
|
||||
.map(|id| id.to_proto());
|
||||
|
||||
if let Some(id) = id.clone() {
|
||||
if let Some(variant) = item.to_state_proto(cx) {
|
||||
let view = Some(proto::View {
|
||||
id: Some(id.clone()),
|
||||
leader_id,
|
||||
variant: Some(variant),
|
||||
});
|
||||
|
||||
is_project_item = item.is_project_item(cx);
|
||||
update = proto::UpdateActiveView {
|
||||
view,
|
||||
// TODO: once v0.124.0 is retired we can stop sending these
|
||||
id: Some(id),
|
||||
leader_id,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -3789,10 +3890,8 @@ impl WorkspaceStore {
|
|||
pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
|
||||
Self {
|
||||
workspaces: Default::default(),
|
||||
followers: Default::default(),
|
||||
_subscriptions: vec![
|
||||
client.add_request_handler(cx.weak_model(), Self::handle_follow),
|
||||
client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
|
||||
client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
|
||||
],
|
||||
client,
|
||||
|
@ -3807,25 +3906,10 @@ impl WorkspaceStore {
|
|||
) -> Option<()> {
|
||||
let active_call = ActiveCall::try_global(cx)?;
|
||||
let room_id = active_call.read(cx).room()?.read(cx).id();
|
||||
let follower_ids: Vec<_> = self
|
||||
.followers
|
||||
.iter()
|
||||
.filter_map(|follower| {
|
||||
if follower.project_id == project_id || project_id.is_none() {
|
||||
Some(follower.peer_id.into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
if follower_ids.is_empty() {
|
||||
return None;
|
||||
}
|
||||
self.client
|
||||
.send(proto::UpdateFollowers {
|
||||
room_id,
|
||||
project_id,
|
||||
follower_ids,
|
||||
variant: Some(update),
|
||||
})
|
||||
.log_err()
|
||||
|
@ -3862,36 +3946,20 @@ impl WorkspaceStore {
|
|||
response.active_view_id = Some(active_view_id);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(active_view) = handler_response.active_view.clone() {
|
||||
if workspace.project.read(cx).remote_id() == follower.project_id {
|
||||
response.active_view = Some(active_view)
|
||||
}
|
||||
}
|
||||
})
|
||||
.is_ok()
|
||||
});
|
||||
|
||||
if let Err(ix) = this.followers.binary_search(&follower) {
|
||||
this.followers.insert(ix, follower);
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
})?
|
||||
}
|
||||
|
||||
async fn handle_unfollow(
|
||||
model: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::Unfollow>,
|
||||
_: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
model.update(&mut cx, |this, _| {
|
||||
let follower = Follower {
|
||||
project_id: envelope.payload.project_id,
|
||||
peer_id: envelope.original_sender_id()?,
|
||||
};
|
||||
if let Ok(ix) = this.followers.binary_search(&follower) {
|
||||
this.followers.remove(ix);
|
||||
}
|
||||
Ok(())
|
||||
})?
|
||||
}
|
||||
|
||||
async fn handle_update_followers(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::UpdateFollowers>,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue