Follower simplification (#8026)

Release Notes:

- Improved reliability of following
This commit is contained in:
Conrad Irwin 2024-02-20 09:41:37 -07:00 committed by GitHub
parent db0eaca2e5
commit b14d576349
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 143 additions and 76 deletions

View file

@ -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>,