From 170291ff96baefded554a3524e64fe2681621d5b Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 21 Nov 2023 19:57:24 +0100 Subject: [PATCH 1/6] Start decoupling workspace and call crates --- Cargo.lock | 1 + crates/workspace2/Cargo.toml | 1 + crates/workspace2/src/pane_group.rs | 7 +- crates/workspace2/src/workspace2.rs | 382 ++++++++++++++++++---------- 4 files changed, 246 insertions(+), 145 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6aa94b08d0..85f474b046 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11319,6 +11319,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-recursion 1.0.5", + "async-trait", "bincode", "call2", "client2", diff --git a/crates/workspace2/Cargo.toml b/crates/workspace2/Cargo.toml index f3f10d2015..bddf019eb5 100644 --- a/crates/workspace2/Cargo.toml +++ b/crates/workspace2/Cargo.toml @@ -37,6 +37,7 @@ theme2 = { path = "../theme2" } util = { path = "../util" } ui = { package = "ui2", path = "../ui2" } +async-trait.workspace = true async-recursion = "1.0.0" itertools = "0.10" bincode = "1.2.1" diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index bd827a6dd7..80e002a429 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -127,7 +127,6 @@ impl PaneGroup { &self, project: &Model, follower_states: &HashMap, FollowerState>, - active_call: Option<&Model>, active_pane: &View, zoomed: Option<&AnyWeakView>, app_state: &Arc, @@ -137,7 +136,6 @@ impl PaneGroup { project, 0, follower_states, - active_call, active_pane, zoomed, app_state, @@ -199,7 +197,6 @@ impl Member { project: &Model, basis: usize, follower_states: &HashMap, FollowerState>, - active_call: Option<&Model>, active_pane: &View, zoomed: Option<&AnyWeakView>, app_state: &Arc, @@ -234,7 +231,6 @@ impl Member { project, basis + 1, follower_states, - active_call, active_pane, zoomed, app_state, @@ -556,7 +552,7 @@ impl PaneAxis { project: &Model, basis: usize, follower_states: &HashMap, FollowerState>, - active_call: Option<&Model>, + active_pane: &View, zoomed: Option<&AnyWeakView>, app_state: &Arc, @@ -578,7 +574,6 @@ impl PaneAxis { project, basis, follower_states, - active_call, active_pane, zoomed, app_state, diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 22a7b57058..64f6e5963d 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -15,7 +15,8 @@ mod status_bar; mod toolbar; mod workspace_settings; -use anyhow::{anyhow, Context as _, Result}; +use anyhow::{anyhow, bail, Context as _, Result}; +use async_trait::async_trait; use call2::ActiveCall; use client2::{ proto::{self, PeerId}, @@ -33,8 +34,8 @@ use gpui::{ AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter, FocusHandle, FocusableView, GlobalPixels, InteractiveElement, KeyContext, ManagedView, Model, ModelContext, ParentElement, PathPromptOptions, Point, PromptLevel, Render, Size, Styled, Subscription, Task, - View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, - WindowOptions, + View, ViewContext, VisualContext, WeakModel, WeakView, WindowBounds, WindowContext, + WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; use itertools::Itertools; @@ -408,6 +409,177 @@ pub enum Event { WorkspaceCreated(WeakView), } +#[async_trait(?Send)] +trait CallHandler { + fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()>; + fn shared_screen_for_peer( + &self, + peer_id: PeerId, + pane: &View, + cx: &mut ViewContext, + ) -> Option>; + fn follower_states_mut(&mut self) -> &mut HashMap, FollowerState>; + fn follower_states(&self) -> &HashMap, FollowerState>; + fn room_id(&self, cx: &AppContext) -> Option; + fn is_in_room(&self, cx: &mut ViewContext) -> bool { + self.room_id(cx).is_some() + } + fn hang_up(&self, cx: AsyncWindowContext) -> Result>>; + fn active_project(&self, cx: &AppContext) -> Option>; +} +struct Call { + follower_states: HashMap, FollowerState>, + active_call: Option<(Model, Vec)>, + parent_workspace: WeakView, +} + +impl Call { + fn new(parent_workspace: WeakView, cx: &mut ViewContext<'_, Workspace>) -> Self { + let mut active_call = None; + if cx.has_global::>() { + let call = cx.global::>().clone(); + let subscriptions = vec![cx.subscribe(&call, Self::on_active_call_event)]; + active_call = Some((call, subscriptions)); + } + Self { + follower_states: Default::default(), + active_call, + parent_workspace, + } + } + fn on_active_call_event( + workspace: &mut Workspace, + _: Model, + event: &call2::room::Event, + cx: &mut ViewContext, + ) { + match event { + call2::room::Event::ParticipantLocationChanged { participant_id } + | call2::room::Event::RemoteVideoTracksChanged { participant_id } => { + workspace.leader_updated(*participant_id, cx); + } + _ => {} + } + } +} + +#[async_trait(?Send)] +impl CallHandler for Call { + fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { + cx.notify(); + + let (call, _) = self.active_call.as_ref()?; + let room = call.read(cx).room()?.read(cx); + let participant = room.remote_participant_for_peer_id(leader_id)?; + let mut items_to_activate = Vec::new(); + + let leader_in_this_app; + let leader_in_this_project; + match participant.location { + call2::ParticipantLocation::SharedProject { project_id } => { + leader_in_this_app = true; + leader_in_this_project = Some(project_id) + == self + .parent_workspace + .update(cx, |this, cx| this.project.read(cx).remote_id()) + .log_err() + .flatten(); + } + call2::ParticipantLocation::UnsharedProject => { + leader_in_this_app = true; + leader_in_this_project = false; + } + call2::ParticipantLocation::External => { + leader_in_this_app = false; + leader_in_this_project = false; + } + }; + + for (pane, state) in &self.follower_states { + if state.leader_id != leader_id { + continue; + } + if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) { + if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) { + if leader_in_this_project || !item.is_project_item(cx) { + items_to_activate.push((pane.clone(), item.boxed_clone())); + } + } else { + log::warn!( + "unknown view id {:?} for leader {:?}", + active_view_id, + leader_id + ); + } + continue; + } + // todo!() + // if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { + // items_to_activate.push((pane.clone(), Box::new(shared_screen))); + // } + } + + for (pane, item) in items_to_activate { + let pane_was_focused = pane.read(cx).has_focus(cx); + if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { + pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx)); + } else { + pane.update(cx, |pane, mut cx| { + pane.add_item(item.boxed_clone(), false, false, None, &mut cx) + }); + } + + if pane_was_focused { + pane.update(cx, |pane, cx| pane.focus_active_item(cx)); + } + } + + None + } + + fn shared_screen_for_peer( + &self, + peer_id: PeerId, + pane: &View, + cx: &mut ViewContext, + ) -> Option> { + let (call, _) = self.active_call.as_ref()?; + let room = call.read(cx).room()?.read(cx); + let participant = room.remote_participant_for_peer_id(peer_id)?; + let track = participant.video_tracks.values().next()?.clone(); + let user = participant.user.clone(); + todo!(); + // for item in pane.read(cx).items_of_type::() { + // if item.read(cx).peer_id == peer_id { + // return Box::new(Some(item)); + // } + // } + + // Some(Box::new(cx.build_view(|cx| { + // SharedScreen::new(&track, peer_id, user.clone(), cx) + // }))) + } + + fn follower_states_mut(&mut self) -> &mut HashMap, FollowerState> { + &mut self.follower_states + } + fn follower_states(&self) -> &HashMap, FollowerState> { + &self.follower_states + } + fn room_id(&self, cx: &AppContext) -> Option { + Some(self.active_call.as_ref()?.0.read(cx).room()?.read(cx).id()) + } + fn hang_up(&self, mut cx: AsyncWindowContext) -> Result>> { + let Some((call, _)) = self.active_call.as_ref() else { + bail!("Cannot exit a call; not in a call"); + }; + + call.update(&mut cx, |this, cx| this.hang_up(cx)) + } + fn active_project(&self, cx: &AppContext) -> Option> { + ActiveCall::global(cx).read(cx).location().cloned() + } +} pub struct Workspace { window_self: WindowHandle, weak_self: WeakView, @@ -428,10 +600,9 @@ pub struct Workspace { titlebar_item: Option, notifications: Vec<(TypeId, usize, Box)>, project: Model, - follower_states: HashMap, FollowerState>, + call_handler: Box, last_leaders_by_pane: HashMap, PeerId>, window_edited: bool, - active_call: Option<(Model, Vec)>, leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, database_id: WorkspaceId, app_state: Arc, @@ -550,9 +721,19 @@ impl Workspace { mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>(); let _apply_leader_updates = cx.spawn(|this, mut cx| async move { while let Some((leader_id, update)) = leader_updates_rx.next().await { - Self::process_leader_update(&this, leader_id, update, &mut cx) + let mut cx2 = cx.clone(); + let t = this.clone(); + + Workspace::process_leader_update(&this, leader_id, update, &mut cx) .await .log_err(); + + // this.update(&mut cx, |this, cxx| { + // this.call_handler + // .process_leader_update(leader_id, update, cx2) + // })? + // .await + // .log_err(); } Ok(()) @@ -585,14 +766,6 @@ impl Workspace { // drag_and_drop.register_container(weak_handle.clone()); // }); - let mut active_call = None; - if cx.has_global::>() { - let call = cx.global::>().clone(); - let mut subscriptions = Vec::new(); - subscriptions.push(cx.subscribe(&call, Self::on_active_call_event)); - active_call = Some((call, subscriptions)); - } - let subscriptions = vec![ cx.observe_window_activation(Self::on_window_activation_changed), cx.observe_window_bounds(move |_, cx| { @@ -652,10 +825,11 @@ impl Workspace { bottom_dock, right_dock, project: project.clone(), - follower_states: Default::default(), + last_leaders_by_pane: Default::default(), window_edited: false, - active_call, + + call_handler: Box::new(Call::new(weak_handle.clone(), cx)), database_id: workspace_id, app_state, _observe_current_user, @@ -1102,7 +1276,7 @@ impl Workspace { cx: &mut ViewContext, ) -> Task> { //todo!(saveing) - let active_call = self.active_call().cloned(); + let window = cx.window_handle(); cx.spawn(|this, mut cx| async move { @@ -1113,27 +1287,27 @@ impl Workspace { .count() })?; - if let Some(active_call) = active_call { - if !quitting - && workspace_count == 1 - && active_call.read_with(&cx, |call, _| call.room().is_some())? - { - let answer = window.update(&mut cx, |_, cx| { - cx.prompt( - PromptLevel::Warning, - "Do you want to leave the current call?", - &["Close window and hang up", "Cancel"], - ) - })?; + if !quitting + && workspace_count == 1 + && this + .update(&mut cx, |this, cx| this.call_handler.is_in_room(cx)) + .log_err() + .unwrap_or_default() + { + let answer = window.update(&mut cx, |_, cx| { + cx.prompt( + PromptLevel::Warning, + "Do you want to leave the current call?", + &["Close window and hang up", "Cancel"], + ) + })?; - if answer.await.log_err() == Some(1) { - return anyhow::Ok(false); - } else { - active_call - .update(&mut cx, |call, cx| call.hang_up(cx))? - .await - .log_err(); - } + if answer.await.log_err() == Some(1) { + return anyhow::Ok(false); + } else { + this.update(&mut cx, |this, cx| this.call_handler.hang_up(cx.to_async()))?? + .await + .log_err(); } } @@ -2238,7 +2412,7 @@ impl Workspace { } fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext) { - self.follower_states.retain(|_, state| { + self.call_handler.follower_states_mut().retain(|_, state| { if state.leader_id == peer_id { for item in state.items_by_leader_view_id.values() { item.set_leader_peer_id(None, cx); @@ -2391,19 +2565,19 @@ impl Workspace { // } pub fn unfollow(&mut self, pane: &View, cx: &mut ViewContext) -> Option { - let state = self.follower_states.remove(pane)?; + let follower_states = self.call_handler.follower_states_mut(); + let state = follower_states.remove(pane)?; let leader_id = state.leader_id; for (_, item) in state.items_by_leader_view_id { item.set_leader_peer_id(None, cx); } - if self - .follower_states + if follower_states .values() .all(|state| state.leader_id != state.leader_id) { let project_id = self.project.read(cx).remote_id(); - let room_id = self.active_call()?.read(cx).room()?.read(cx).id(); + let room_id = self.call_handler.room_id(cx)?; self.app_state .client .send(proto::Unfollow { @@ -2614,7 +2788,7 @@ impl Workspace { 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 { + for (_, state) in this.call_handler.follower_states_mut() { if state.leader_id == leader_id { state.active_view_id = if let Some(active_view_id) = update_active_view.id.clone() { @@ -2637,7 +2811,7 @@ impl Workspace { let mut tasks = Vec::new(); this.update(cx, |this, cx| { let project = this.project.clone(); - for (_, state) in &mut this.follower_states { + for (_, state) in this.call_handler.follower_states_mut() { if state.leader_id == leader_id { let view_id = ViewId::from_proto(id.clone())?; if let Some(item) = state.items_by_leader_view_id.get(&view_id) { @@ -2651,7 +2825,8 @@ impl Workspace { } proto::update_followers::Variant::CreateView(view) => { let panes = this.update(cx, |this, _| { - this.follower_states + this.call_handler + .follower_states() .iter() .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane)) .cloned() @@ -2711,7 +2886,7 @@ impl Workspace { for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane { let items = futures::future::try_join_all(item_tasks).await?; this.update(cx, |this, cx| { - let state = this.follower_states.get_mut(&pane)?; + let state = this.call_handler.follower_states_mut().get_mut(&pane)?; for (id, item) in leader_view_ids.into_iter().zip(items) { item.set_leader_peer_id(Some(leader_id), cx); state.items_by_leader_view_id.insert(id, item); @@ -2768,74 +2943,14 @@ impl Workspace { } pub fn leader_for_pane(&self, pane: &View) -> Option { - self.follower_states.get(pane).map(|state| state.leader_id) + self.call_handler + .follower_states() + .get(pane) + .map(|state| state.leader_id) } fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { - cx.notify(); - - let call = self.active_call()?; - let room = call.read(cx).room()?.read(cx); - let participant = room.remote_participant_for_peer_id(leader_id)?; - let mut items_to_activate = Vec::new(); - - let leader_in_this_app; - let leader_in_this_project; - match participant.location { - call2::ParticipantLocation::SharedProject { project_id } => { - leader_in_this_app = true; - leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id(); - } - call2::ParticipantLocation::UnsharedProject => { - leader_in_this_app = true; - leader_in_this_project = false; - } - call2::ParticipantLocation::External => { - leader_in_this_app = false; - leader_in_this_project = false; - } - }; - - for (pane, state) in &self.follower_states { - if state.leader_id != leader_id { - continue; - } - if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) { - if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) { - if leader_in_this_project || !item.is_project_item(cx) { - items_to_activate.push((pane.clone(), item.boxed_clone())); - } - } else { - log::warn!( - "unknown view id {:?} for leader {:?}", - active_view_id, - leader_id - ); - } - continue; - } - // todo!() - // if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { - // items_to_activate.push((pane.clone(), Box::new(shared_screen))); - // } - } - - for (pane, item) in items_to_activate { - let pane_was_focused = pane.read(cx).has_focus(cx); - if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { - pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx)); - } else { - pane.update(cx, |pane, cx| { - pane.add_item(item.boxed_clone(), false, false, None, cx) - }); - } - - if pane_was_focused { - pane.update(cx, |pane, cx| pane.focus_active_item(cx)); - } - } - - None + self.call_handler.leader_updated(leader_id, cx) } // todo!() @@ -2886,25 +3001,6 @@ impl Workspace { } } - fn active_call(&self) -> Option<&Model> { - self.active_call.as_ref().map(|(call, _)| call) - } - - fn on_active_call_event( - &mut self, - _: Model, - event: &call2::room::Event, - cx: &mut ViewContext, - ) { - match event { - call2::room::Event::ParticipantLocationChanged { participant_id } - | call2::room::Event::RemoteVideoTracksChanged { participant_id } => { - self.leader_updated(*participant_id, cx); - } - _ => {} - } - } - pub fn database_id(&self) -> WorkspaceId { self.database_id } @@ -3671,8 +3767,7 @@ impl Render for Workspace { .flex_1() .child(self.center.render( &self.project, - &self.follower_states, - self.active_call(), + &self.call_handler.follower_states(), &self.active_pane, self.zoomed.as_ref(), &self.app_state, @@ -3845,11 +3940,12 @@ impl WorkspaceStore { update: proto::update_followers::Variant, cx: &AppContext, ) -> Option<()> { - if !cx.has_global::>() { - return None; - } - - let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id(); + let room_id = self.workspaces.iter().next().and_then(|workspace| { + workspace + .read_with(cx, |this, cx| this.call_handler.room_id(cx)) + .log_err() + .flatten() + })?; let follower_ids: Vec<_> = self .followers .iter() @@ -3885,9 +3981,17 @@ impl WorkspaceStore { project_id: envelope.payload.project_id, peer_id: envelope.original_sender_id()?, }; - let active_project = ActiveCall::global(cx).read(cx).location().cloned(); - let mut response = proto::FollowResponse::default(); + let active_project = this + .workspaces + .iter() + .next() + .and_then(|workspace| { + workspace + .read_with(cx, |this, cx| this.call_handler.active_project(cx)) + .log_err() + }) + .flatten(); for workspace in &this.workspaces { workspace .update(cx, |workspace, cx| { From ebccdb64bcf7b62d65d99e3065bb932c599b1d27 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 21 Nov 2023 20:18:35 +0100 Subject: [PATCH 2/6] Move CallHandler impl into call2 --- Cargo.lock | 1 + crates/call2/Cargo.toml | 2 +- crates/call2/src/call2.rs | 154 +++++++++++++++++++++ crates/workspace2/Cargo.toml | 1 - crates/workspace2/src/pane_group.rs | 1 - crates/workspace2/src/workspace2.rs | 202 ++++++---------------------- 6 files changed, 196 insertions(+), 165 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85f474b046..17bda4458c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1193,6 +1193,7 @@ dependencies = [ "serde_json", "settings2", "util", + "workspace2", ] [[package]] diff --git a/crates/call2/Cargo.toml b/crates/call2/Cargo.toml index 9e13463680..500931cc11 100644 --- a/crates/call2/Cargo.toml +++ b/crates/call2/Cargo.toml @@ -31,7 +31,7 @@ media = { path = "../media" } project = { package = "project2", path = "../project2" } settings = { package = "settings2", path = "../settings2" } util = { path = "../util" } - +workspace = {package = "workspace2", path = "../workspace2"} anyhow.workspace = true async-broadcast = "0.4" futures.workspace = true diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index 1f11e0650d..34a9aabe14 100644 --- a/crates/call2/src/call2.rs +++ b/crates/call2/src/call2.rs @@ -505,6 +505,160 @@ pub fn report_call_event_for_channel( ) } +struct Call { + follower_states: HashMap, FollowerState>, + active_call: Option<(Model, Vec)>, + parent_workspace: WeakView, +} + +impl Call { + fn new(parent_workspace: WeakView, cx: &mut ViewContext<'_, Workspace>) -> Self { + let mut active_call = None; + if cx.has_global::>() { + let call = cx.global::>().clone(); + let subscriptions = vec![cx.subscribe(&call, Self::on_active_call_event)]; + active_call = Some((call, subscriptions)); + } + Self { + follower_states: Default::default(), + active_call, + parent_workspace, + } + } + fn on_active_call_event( + workspace: &mut Workspace, + _: Model, + event: &call2::room::Event, + cx: &mut ViewContext, + ) { + match event { + call2::room::Event::ParticipantLocationChanged { participant_id } + | call2::room::Event::RemoteVideoTracksChanged { participant_id } => { + workspace.leader_updated(*participant_id, cx); + } + _ => {} + } + } +} + +#[async_trait(?Send)] +impl CallHandler for Call { + fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { + cx.notify(); + + let (call, _) = self.active_call.as_ref()?; + let room = call.read(cx).room()?.read(cx); + let participant = room.remote_participant_for_peer_id(leader_id)?; + let mut items_to_activate = Vec::new(); + + let leader_in_this_app; + let leader_in_this_project; + match participant.location { + call2::ParticipantLocation::SharedProject { project_id } => { + leader_in_this_app = true; + leader_in_this_project = Some(project_id) + == self + .parent_workspace + .update(cx, |this, cx| this.project.read(cx).remote_id()) + .log_err() + .flatten(); + } + call2::ParticipantLocation::UnsharedProject => { + leader_in_this_app = true; + leader_in_this_project = false; + } + call2::ParticipantLocation::External => { + leader_in_this_app = false; + leader_in_this_project = false; + } + }; + + for (pane, state) in &self.follower_states { + if state.leader_id != leader_id { + continue; + } + if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) { + if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) { + if leader_in_this_project || !item.is_project_item(cx) { + items_to_activate.push((pane.clone(), item.boxed_clone())); + } + } else { + log::warn!( + "unknown view id {:?} for leader {:?}", + active_view_id, + leader_id + ); + } + continue; + } + // todo!() + // if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { + // items_to_activate.push((pane.clone(), Box::new(shared_screen))); + // } + } + + for (pane, item) in items_to_activate { + let pane_was_focused = pane.read(cx).has_focus(cx); + if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { + pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx)); + } else { + pane.update(cx, |pane, mut cx| { + pane.add_item(item.boxed_clone(), false, false, None, &mut cx) + }); + } + + if pane_was_focused { + pane.update(cx, |pane, cx| pane.focus_active_item(cx)); + } + } + + None + } + + fn shared_screen_for_peer( + &self, + peer_id: PeerId, + pane: &View, + cx: &mut ViewContext, + ) -> Option> { + let (call, _) = self.active_call.as_ref()?; + let room = call.read(cx).room()?.read(cx); + let participant = room.remote_participant_for_peer_id(peer_id)?; + let track = participant.video_tracks.values().next()?.clone(); + let user = participant.user.clone(); + todo!(); + // for item in pane.read(cx).items_of_type::() { + // if item.read(cx).peer_id == peer_id { + // return Box::new(Some(item)); + // } + // } + + // Some(Box::new(cx.build_view(|cx| { + // SharedScreen::new(&track, peer_id, user.clone(), cx) + // }))) + } + + fn follower_states_mut(&mut self) -> &mut HashMap, FollowerState> { + &mut self.follower_states + } + fn follower_states(&self) -> &HashMap, FollowerState> { + &self.follower_states + } + fn room_id(&self, cx: &AppContext) -> Option { + Some(self.active_call.as_ref()?.0.read(cx).room()?.read(cx).id()) + } + fn hang_up(&self, mut cx: AsyncWindowContext) -> Result>> { + let Some((call, _)) = self.active_call.as_ref() else { + bail!("Cannot exit a call; not in a call"); + }; + + call.update(&mut cx, |this, cx| this.hang_up(cx)) + } + fn active_project(&self, cx: &AppContext) -> Option> { + ActiveCall::global(cx).read(cx).location().cloned() + } +} + #[cfg(test)] mod test { use gpui::TestAppContext; diff --git a/crates/workspace2/Cargo.toml b/crates/workspace2/Cargo.toml index bddf019eb5..c327132a78 100644 --- a/crates/workspace2/Cargo.toml +++ b/crates/workspace2/Cargo.toml @@ -20,7 +20,6 @@ test-support = [ [dependencies] db2 = { path = "../db2" } -call2 = { path = "../call2" } client2 = { path = "../client2" } collections = { path = "../collections" } # context_menu = { path = "../context_menu" } diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index 80e002a429..eeea0bd365 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -1,6 +1,5 @@ use crate::{AppState, FollowerState, Pane, Workspace}; use anyhow::{anyhow, bail, Result}; -use call2::ActiveCall; use collections::HashMap; use db2::sqlez::{ bindable::{Bind, Column, StaticColumnCount}, diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 64f6e5963d..05e994b74f 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -17,7 +17,6 @@ mod workspace_settings; use anyhow::{anyhow, bail, Context as _, Result}; use async_trait::async_trait; -use call2::ActiveCall; use client2::{ proto::{self, PeerId}, Client, TypedEnvelope, UserStore, @@ -410,7 +409,7 @@ pub enum Event { } #[async_trait(?Send)] -trait CallHandler { +pub trait CallHandler { fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()>; fn shared_screen_for_peer( &self, @@ -427,159 +426,7 @@ trait CallHandler { fn hang_up(&self, cx: AsyncWindowContext) -> Result>>; fn active_project(&self, cx: &AppContext) -> Option>; } -struct Call { - follower_states: HashMap, FollowerState>, - active_call: Option<(Model, Vec)>, - parent_workspace: WeakView, -} -impl Call { - fn new(parent_workspace: WeakView, cx: &mut ViewContext<'_, Workspace>) -> Self { - let mut active_call = None; - if cx.has_global::>() { - let call = cx.global::>().clone(); - let subscriptions = vec![cx.subscribe(&call, Self::on_active_call_event)]; - active_call = Some((call, subscriptions)); - } - Self { - follower_states: Default::default(), - active_call, - parent_workspace, - } - } - fn on_active_call_event( - workspace: &mut Workspace, - _: Model, - event: &call2::room::Event, - cx: &mut ViewContext, - ) { - match event { - call2::room::Event::ParticipantLocationChanged { participant_id } - | call2::room::Event::RemoteVideoTracksChanged { participant_id } => { - workspace.leader_updated(*participant_id, cx); - } - _ => {} - } - } -} - -#[async_trait(?Send)] -impl CallHandler for Call { - fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { - cx.notify(); - - let (call, _) = self.active_call.as_ref()?; - let room = call.read(cx).room()?.read(cx); - let participant = room.remote_participant_for_peer_id(leader_id)?; - let mut items_to_activate = Vec::new(); - - let leader_in_this_app; - let leader_in_this_project; - match participant.location { - call2::ParticipantLocation::SharedProject { project_id } => { - leader_in_this_app = true; - leader_in_this_project = Some(project_id) - == self - .parent_workspace - .update(cx, |this, cx| this.project.read(cx).remote_id()) - .log_err() - .flatten(); - } - call2::ParticipantLocation::UnsharedProject => { - leader_in_this_app = true; - leader_in_this_project = false; - } - call2::ParticipantLocation::External => { - leader_in_this_app = false; - leader_in_this_project = false; - } - }; - - for (pane, state) in &self.follower_states { - if state.leader_id != leader_id { - continue; - } - if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) { - if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) { - if leader_in_this_project || !item.is_project_item(cx) { - items_to_activate.push((pane.clone(), item.boxed_clone())); - } - } else { - log::warn!( - "unknown view id {:?} for leader {:?}", - active_view_id, - leader_id - ); - } - continue; - } - // todo!() - // if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { - // items_to_activate.push((pane.clone(), Box::new(shared_screen))); - // } - } - - for (pane, item) in items_to_activate { - let pane_was_focused = pane.read(cx).has_focus(cx); - if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { - pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx)); - } else { - pane.update(cx, |pane, mut cx| { - pane.add_item(item.boxed_clone(), false, false, None, &mut cx) - }); - } - - if pane_was_focused { - pane.update(cx, |pane, cx| pane.focus_active_item(cx)); - } - } - - None - } - - fn shared_screen_for_peer( - &self, - peer_id: PeerId, - pane: &View, - cx: &mut ViewContext, - ) -> Option> { - let (call, _) = self.active_call.as_ref()?; - let room = call.read(cx).room()?.read(cx); - let participant = room.remote_participant_for_peer_id(peer_id)?; - let track = participant.video_tracks.values().next()?.clone(); - let user = participant.user.clone(); - todo!(); - // for item in pane.read(cx).items_of_type::() { - // if item.read(cx).peer_id == peer_id { - // return Box::new(Some(item)); - // } - // } - - // Some(Box::new(cx.build_view(|cx| { - // SharedScreen::new(&track, peer_id, user.clone(), cx) - // }))) - } - - fn follower_states_mut(&mut self) -> &mut HashMap, FollowerState> { - &mut self.follower_states - } - fn follower_states(&self) -> &HashMap, FollowerState> { - &self.follower_states - } - fn room_id(&self, cx: &AppContext) -> Option { - Some(self.active_call.as_ref()?.0.read(cx).room()?.read(cx).id()) - } - fn hang_up(&self, mut cx: AsyncWindowContext) -> Result>> { - let Some((call, _)) = self.active_call.as_ref() else { - bail!("Cannot exit a call; not in a call"); - }; - - call.update(&mut cx, |this, cx| this.hang_up(cx)) - } - fn active_project(&self, cx: &AppContext) -> Option> { - ActiveCall::global(cx).read(cx).location().cloned() - } -} pub struct Workspace { window_self: WindowHandle, weak_self: WeakView, @@ -611,6 +458,7 @@ pub struct Workspace { _observe_current_user: Task>, _schedule_serialize: Option>, pane_history_timestamp: Arc, + call_factory: CallFactory, } impl EventEmitter for Workspace {} @@ -630,11 +478,13 @@ struct FollowerState { enum WorkspaceBounds {} +type CallFactory = fn(WeakView, &mut ViewContext) -> Box; impl Workspace { pub fn new( workspace_id: WorkspaceId, project: Model, app_state: Arc, + call_factory: CallFactory, cx: &mut ViewContext, ) -> Self { cx.observe(&project, |_, _, cx| cx.notify()).detach(); @@ -829,7 +679,7 @@ impl Workspace { last_leaders_by_pane: Default::default(), window_edited: false, - call_handler: Box::new(Call::new(weak_handle.clone(), cx)), + call_handler: call_factory(weak_handle.clone(), cx), database_id: workspace_id, app_state, _observe_current_user, @@ -839,6 +689,7 @@ impl Workspace { subscriptions, pane_history_timestamp, workspace_actions: Default::default(), + call_factory, } } @@ -846,6 +697,7 @@ impl Workspace { abs_paths: Vec, app_state: Arc, requesting_window: Option>, + call_factory: CallFactory, cx: &mut AppContext, ) -> Task< anyhow::Result<( @@ -896,7 +748,13 @@ impl Workspace { let window = if let Some(window) = requesting_window { cx.update_window(window.into(), |old_workspace, cx| { cx.replace_root_view(|cx| { - Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx) + Workspace::new( + workspace_id, + project_handle.clone(), + app_state.clone(), + call_factory, + cx, + ) }); })?; window @@ -942,7 +800,13 @@ impl Workspace { let project_handle = project_handle.clone(); move |cx| { cx.build_view(|cx| { - Workspace::new(workspace_id, project_handle, app_state, cx) + Workspace::new( + workspace_id, + project_handle, + app_state, + call_factory, + cx, + ) }) } })? @@ -1203,7 +1067,13 @@ impl Workspace { if self.project.read(cx).is_local() { Task::Ready(Some(Ok(callback(self, cx)))) } else { - let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx); + let task = Self::new_local( + Vec::new(), + self.app_state.clone(), + None, + self.call_factory, + cx, + ); cx.spawn(|_vh, mut cx| async move { let (workspace, _) = task.await?; workspace.update(&mut cx, callback) @@ -1432,7 +1302,7 @@ impl Workspace { Some(self.prepare_to_close(false, cx)) }; let app_state = self.app_state.clone(); - + let call_factory = self.call_factory; cx.spawn(|_, mut cx| async move { let window_to_replace = if let Some(close_task) = close_task { if !close_task.await? { @@ -1442,7 +1312,7 @@ impl Workspace { } else { None }; - cx.update(|_, cx| open_paths(&paths, &app_state, window_to_replace, cx))? + cx.update(|_, cx| open_paths(&paths, &app_state, window_to_replace, call_factory, cx))? .await?; Ok(()) }) @@ -4331,6 +4201,7 @@ pub fn open_paths( abs_paths: &[PathBuf], app_state: &Arc, requesting_window: Option>, + call_factory: CallFactory, cx: &mut AppContext, ) -> Task< anyhow::Result<( @@ -4357,7 +4228,13 @@ pub fn open_paths( todo!() } else { cx.update(move |cx| { - Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx) + Workspace::new_local( + abs_paths, + app_state.clone(), + requesting_window, + call_factory, + cx, + ) })? .await } @@ -4368,8 +4245,9 @@ pub fn open_new( app_state: &Arc, cx: &mut AppContext, init: impl FnOnce(&mut Workspace, &mut ViewContext) + 'static + Send, + call_factory: CallFactory, ) -> Task<()> { - let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx); + let task = Workspace::new_local(Vec::new(), app_state.clone(), None, call_factory, cx); cx.spawn(|mut cx| async move { if let Some((workspace, opened_paths)) = task.await.log_err() { workspace From abe5a9c85f909414d044bc1691af756fd64177be Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 21 Nov 2023 20:51:53 +0100 Subject: [PATCH 3/6] Finish up decoupling workspace from call --- Cargo.lock | 1 + crates/call2/Cargo.toml | 1 + crates/call2/src/call2.rs | 149 ++++++++++------------------ crates/workspace2/src/workspace2.rs | 126 ++++++++++++----------- crates/zed2/src/main.rs | 2 +- 5 files changed, 126 insertions(+), 153 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17bda4458c..b96e12a0fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1175,6 +1175,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-broadcast", + "async-trait", "audio2", "client2", "collections", diff --git a/crates/call2/Cargo.toml b/crates/call2/Cargo.toml index 500931cc11..43e19b4ccb 100644 --- a/crates/call2/Cargo.toml +++ b/crates/call2/Cargo.toml @@ -32,6 +32,7 @@ project = { package = "project2", path = "../project2" } settings = { package = "settings2", path = "../settings2" } util = { path = "../util" } workspace = {package = "workspace2", path = "../workspace2"} +async-trait.workspace = true anyhow.workspace = true async-broadcast = "0.4" futures.workspace = true diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index 34a9aabe14..18576d4657 100644 --- a/crates/call2/src/call2.rs +++ b/crates/call2/src/call2.rs @@ -2,24 +2,29 @@ pub mod call_settings; pub mod participant; pub mod room; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, bail, Result}; +use async_trait::async_trait; use audio::Audio; use call_settings::CallSettings; -use client::{proto, Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE}; +use client::{ + proto::{self, PeerId}, + Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE, +}; use collections::HashSet; use futures::{channel::oneshot, future::Shared, Future, FutureExt}; use gpui::{ - AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task, - WeakModel, + AppContext, AsyncAppContext, AsyncWindowContext, Context, EventEmitter, Model, ModelContext, + Subscription, Task, View, ViewContext, WeakModel, WeakView, }; +pub use participant::ParticipantLocation; use postage::watch; use project::Project; use room::Event; +pub use room::Room; use settings::Settings; use std::sync::Arc; - -pub use participant::ParticipantLocation; -pub use room::Room; +use util::ResultExt; +use workspace::{item::ItemHandle, CallHandler, Pane, Workspace}; pub fn init(client: Arc, user_store: Model, cx: &mut AppContext) { CallSettings::register(cx); @@ -505,35 +510,36 @@ pub fn report_call_event_for_channel( ) } -struct Call { - follower_states: HashMap, FollowerState>, +pub struct Call { active_call: Option<(Model, Vec)>, parent_workspace: WeakView, } impl Call { - fn new(parent_workspace: WeakView, cx: &mut ViewContext<'_, Workspace>) -> Self { + pub fn new( + parent_workspace: WeakView, + cx: &mut ViewContext<'_, Workspace>, + ) -> Box { let mut active_call = None; if cx.has_global::>() { let call = cx.global::>().clone(); let subscriptions = vec![cx.subscribe(&call, Self::on_active_call_event)]; active_call = Some((call, subscriptions)); } - Self { - follower_states: Default::default(), + Box::new(Self { active_call, parent_workspace, - } + }) } fn on_active_call_event( workspace: &mut Workspace, _: Model, - event: &call2::room::Event, + event: &room::Event, cx: &mut ViewContext, ) { match event { - call2::room::Event::ParticipantLocationChanged { participant_id } - | call2::room::Event::RemoteVideoTracksChanged { participant_id } => { + room::Event::ParticipantLocationChanged { participant_id } + | room::Event::RemoteVideoTracksChanged { participant_id } => { workspace.leader_updated(*participant_id, cx); } _ => {} @@ -543,78 +549,6 @@ impl Call { #[async_trait(?Send)] impl CallHandler for Call { - fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { - cx.notify(); - - let (call, _) = self.active_call.as_ref()?; - let room = call.read(cx).room()?.read(cx); - let participant = room.remote_participant_for_peer_id(leader_id)?; - let mut items_to_activate = Vec::new(); - - let leader_in_this_app; - let leader_in_this_project; - match participant.location { - call2::ParticipantLocation::SharedProject { project_id } => { - leader_in_this_app = true; - leader_in_this_project = Some(project_id) - == self - .parent_workspace - .update(cx, |this, cx| this.project.read(cx).remote_id()) - .log_err() - .flatten(); - } - call2::ParticipantLocation::UnsharedProject => { - leader_in_this_app = true; - leader_in_this_project = false; - } - call2::ParticipantLocation::External => { - leader_in_this_app = false; - leader_in_this_project = false; - } - }; - - for (pane, state) in &self.follower_states { - if state.leader_id != leader_id { - continue; - } - if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) { - if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) { - if leader_in_this_project || !item.is_project_item(cx) { - items_to_activate.push((pane.clone(), item.boxed_clone())); - } - } else { - log::warn!( - "unknown view id {:?} for leader {:?}", - active_view_id, - leader_id - ); - } - continue; - } - // todo!() - // if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { - // items_to_activate.push((pane.clone(), Box::new(shared_screen))); - // } - } - - for (pane, item) in items_to_activate { - let pane_was_focused = pane.read(cx).has_focus(cx); - if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { - pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx)); - } else { - pane.update(cx, |pane, mut cx| { - pane.add_item(item.boxed_clone(), false, false, None, &mut cx) - }); - } - - if pane_was_focused { - pane.update(cx, |pane, cx| pane.focus_active_item(cx)); - } - } - - None - } - fn shared_screen_for_peer( &self, peer_id: PeerId, @@ -638,12 +572,6 @@ impl CallHandler for Call { // }))) } - fn follower_states_mut(&mut self) -> &mut HashMap, FollowerState> { - &mut self.follower_states - } - fn follower_states(&self) -> &HashMap, FollowerState> { - &self.follower_states - } fn room_id(&self, cx: &AppContext) -> Option { Some(self.active_call.as_ref()?.0.read(cx).room()?.read(cx).id()) } @@ -657,6 +585,39 @@ impl CallHandler for Call { fn active_project(&self, cx: &AppContext) -> Option> { ActiveCall::global(cx).read(cx).location().cloned() } + fn peer_state( + &mut self, + leader_id: PeerId, + cx: &mut ViewContext, + ) -> Option<(bool, bool)> { + let (call, _) = self.active_call.as_ref()?; + let room = call.read(cx).room()?.read(cx); + let participant = room.remote_participant_for_peer_id(leader_id)?; + + let leader_in_this_app; + let leader_in_this_project; + match participant.location { + ParticipantLocation::SharedProject { project_id } => { + leader_in_this_app = true; + leader_in_this_project = Some(project_id) + == self + .parent_workspace + .update(cx, |this, cx| this.project().read(cx).remote_id()) + .log_err() + .flatten(); + } + ParticipantLocation::UnsharedProject => { + leader_in_this_app = true; + leader_in_this_project = false; + } + ParticipantLocation::External => { + leader_in_this_app = false; + leader_in_this_project = false; + } + }; + + Some((leader_in_this_project, leader_in_this_app)) + } } #[cfg(test)] diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 05e994b74f..754988a605 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -15,7 +15,7 @@ mod status_bar; mod toolbar; mod workspace_settings; -use anyhow::{anyhow, bail, Context as _, Result}; +use anyhow::{anyhow, Context as _, Result}; use async_trait::async_trait; use client2::{ proto::{self, PeerId}, @@ -207,10 +207,10 @@ pub fn init_settings(cx: &mut AppContext) { ItemSettings::register(cx); } -pub fn init(app_state: Arc, cx: &mut AppContext) { +pub fn init(app_state: Arc, cx: &mut AppContext, call_factory: CallFactory) { init_settings(cx); notifications::init(cx); - + cx.set_global(call_factory); // cx.add_global_action({ // let app_state = Arc::downgrade(&app_state); // move |_: &Open, cx: &mut AppContext| { @@ -410,15 +410,13 @@ pub enum Event { #[async_trait(?Send)] pub trait CallHandler { - fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()>; + fn peer_state(&mut self, id: PeerId, cx: &mut ViewContext) -> Option<(bool, bool)>; fn shared_screen_for_peer( &self, peer_id: PeerId, pane: &View, cx: &mut ViewContext, ) -> Option>; - fn follower_states_mut(&mut self) -> &mut HashMap, FollowerState>; - fn follower_states(&self) -> &HashMap, FollowerState>; fn room_id(&self, cx: &AppContext) -> Option; fn is_in_room(&self, cx: &mut ViewContext) -> bool { self.room_id(cx).is_some() @@ -448,6 +446,7 @@ pub struct Workspace { notifications: Vec<(TypeId, usize, Box)>, project: Model, call_handler: Box, + follower_states: HashMap, FollowerState>, last_leaders_by_pane: HashMap, PeerId>, window_edited: bool, leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, @@ -458,7 +457,6 @@ pub struct Workspace { _observe_current_user: Task>, _schedule_serialize: Option>, pane_history_timestamp: Arc, - call_factory: CallFactory, } impl EventEmitter for Workspace {} @@ -484,7 +482,6 @@ impl Workspace { workspace_id: WorkspaceId, project: Model, app_state: Arc, - call_factory: CallFactory, cx: &mut ViewContext, ) -> Self { cx.observe(&project, |_, _, cx| cx.notify()).detach(); @@ -656,6 +653,7 @@ impl Workspace { ]; cx.defer(|this, cx| this.update_window_title(cx)); + let call_factory = cx.global::(); Workspace { window_self: window_handle, weak_self: weak_handle.clone(), @@ -675,7 +673,7 @@ impl Workspace { bottom_dock, right_dock, project: project.clone(), - + follower_states: Default::default(), last_leaders_by_pane: Default::default(), window_edited: false, @@ -689,7 +687,6 @@ impl Workspace { subscriptions, pane_history_timestamp, workspace_actions: Default::default(), - call_factory, } } @@ -697,7 +694,6 @@ impl Workspace { abs_paths: Vec, app_state: Arc, requesting_window: Option>, - call_factory: CallFactory, cx: &mut AppContext, ) -> Task< anyhow::Result<( @@ -748,13 +744,7 @@ impl Workspace { let window = if let Some(window) = requesting_window { cx.update_window(window.into(), |old_workspace, cx| { cx.replace_root_view(|cx| { - Workspace::new( - workspace_id, - project_handle.clone(), - app_state.clone(), - call_factory, - cx, - ) + Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx) }); })?; window @@ -800,13 +790,7 @@ impl Workspace { let project_handle = project_handle.clone(); move |cx| { cx.build_view(|cx| { - Workspace::new( - workspace_id, - project_handle, - app_state, - call_factory, - cx, - ) + Workspace::new(workspace_id, project_handle, app_state, cx) }) } })? @@ -1067,13 +1051,7 @@ impl Workspace { if self.project.read(cx).is_local() { Task::Ready(Some(Ok(callback(self, cx)))) } else { - let task = Self::new_local( - Vec::new(), - self.app_state.clone(), - None, - self.call_factory, - cx, - ); + let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx); cx.spawn(|_vh, mut cx| async move { let (workspace, _) = task.await?; workspace.update(&mut cx, callback) @@ -1302,7 +1280,7 @@ impl Workspace { Some(self.prepare_to_close(false, cx)) }; let app_state = self.app_state.clone(); - let call_factory = self.call_factory; + cx.spawn(|_, mut cx| async move { let window_to_replace = if let Some(close_task) = close_task { if !close_task.await? { @@ -1312,7 +1290,7 @@ impl Workspace { } else { None }; - cx.update(|_, cx| open_paths(&paths, &app_state, window_to_replace, call_factory, cx))? + cx.update(|_, cx| open_paths(&paths, &app_state, window_to_replace, cx))? .await?; Ok(()) }) @@ -2282,7 +2260,7 @@ impl Workspace { } fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext) { - self.call_handler.follower_states_mut().retain(|_, state| { + self.follower_states.retain(|_, state| { if state.leader_id == peer_id { for item in state.items_by_leader_view_id.values() { item.set_leader_peer_id(None, cx); @@ -2435,7 +2413,7 @@ impl Workspace { // } pub fn unfollow(&mut self, pane: &View, cx: &mut ViewContext) -> Option { - let follower_states = self.call_handler.follower_states_mut(); + let follower_states = &mut self.follower_states; let state = follower_states.remove(pane)?; let leader_id = state.leader_id; for (_, item) in state.items_by_leader_view_id { @@ -2658,7 +2636,7 @@ impl Workspace { match update.variant.ok_or_else(|| anyhow!("invalid update"))? { proto::update_followers::Variant::UpdateActiveView(update_active_view) => { this.update(cx, |this, _| { - for (_, state) in this.call_handler.follower_states_mut() { + 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() { @@ -2681,7 +2659,7 @@ impl Workspace { let mut tasks = Vec::new(); this.update(cx, |this, cx| { let project = this.project.clone(); - for (_, state) in this.call_handler.follower_states_mut() { + for (_, state) in &mut this.follower_states { if state.leader_id == leader_id { let view_id = ViewId::from_proto(id.clone())?; if let Some(item) = state.items_by_leader_view_id.get(&view_id) { @@ -2695,8 +2673,7 @@ impl Workspace { } proto::update_followers::Variant::CreateView(view) => { let panes = this.update(cx, |this, _| { - this.call_handler - .follower_states() + this.follower_states .iter() .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane)) .cloned() @@ -2756,7 +2733,7 @@ impl Workspace { for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane { let items = futures::future::try_join_all(item_tasks).await?; this.update(cx, |this, cx| { - let state = this.call_handler.follower_states_mut().get_mut(&pane)?; + let state = this.follower_states.get_mut(&pane)?; for (id, item) in leader_view_ids.into_iter().zip(items) { item.set_leader_peer_id(Some(leader_id), cx); state.items_by_leader_view_id.insert(id, item); @@ -2813,14 +2790,55 @@ impl Workspace { } pub fn leader_for_pane(&self, pane: &View) -> Option { - self.call_handler - .follower_states() - .get(pane) - .map(|state| state.leader_id) + self.follower_states.get(pane).map(|state| state.leader_id) } - fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { - self.call_handler.leader_updated(leader_id, cx) + pub fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { + cx.notify(); + + let (leader_in_this_project, leader_in_this_app) = + self.call_handler.peer_state(leader_id, cx)?; + let mut items_to_activate = Vec::new(); + for (pane, state) in &self.follower_states { + if state.leader_id != leader_id { + continue; + } + if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) { + if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) { + if leader_in_this_project || !item.is_project_item(cx) { + items_to_activate.push((pane.clone(), item.boxed_clone())); + } + } else { + log::warn!( + "unknown view id {:?} for leader {:?}", + active_view_id, + leader_id + ); + } + continue; + } + // todo!() + // if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { + // items_to_activate.push((pane.clone(), Box::new(shared_screen))); + // } + } + + for (pane, item) in items_to_activate { + let pane_was_focused = pane.read(cx).has_focus(cx); + if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { + pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx)); + } else { + pane.update(cx, |pane, mut cx| { + pane.add_item(item.boxed_clone(), false, false, None, &mut cx) + }); + } + + if pane_was_focused { + pane.update(cx, |pane, cx| pane.focus_active_item(cx)); + } + } + + None } // todo!() @@ -3637,7 +3655,7 @@ impl Render for Workspace { .flex_1() .child(self.center.render( &self.project, - &self.call_handler.follower_states(), + &self.follower_states, &self.active_pane, self.zoomed.as_ref(), &self.app_state, @@ -4201,7 +4219,6 @@ pub fn open_paths( abs_paths: &[PathBuf], app_state: &Arc, requesting_window: Option>, - call_factory: CallFactory, cx: &mut AppContext, ) -> Task< anyhow::Result<( @@ -4228,13 +4245,7 @@ pub fn open_paths( todo!() } else { cx.update(move |cx| { - Workspace::new_local( - abs_paths, - app_state.clone(), - requesting_window, - call_factory, - cx, - ) + Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx) })? .await } @@ -4245,9 +4256,8 @@ pub fn open_new( app_state: &Arc, cx: &mut AppContext, init: impl FnOnce(&mut Workspace, &mut ViewContext) + 'static + Send, - call_factory: CallFactory, ) -> Task<()> { - let task = Workspace::new_local(Vec::new(), app_state.clone(), None, call_factory, cx); + let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx); cx.spawn(|mut cx| async move { if let Some((workspace, opened_paths)) = task.await.log_err() { workspace diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 9c42badb85..62d337a716 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -189,7 +189,7 @@ fn main() { // audio::init(Assets, cx); auto_update::init(http.clone(), client::ZED_SERVER_URL.clone(), cx); - workspace::init(app_state.clone(), cx); + workspace::init(app_state.clone(), cx, call::Call::new); // recent_projects::init(cx); go_to_line::init(cx); From 7e7a778d116938d9ef2ff219f7a4e73c952e00ae Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 21 Nov 2023 22:04:02 +0100 Subject: [PATCH 4/6] Move CallFactory into AppState Fix crash caused by double borrow of window handle --- crates/workspace2/src/workspace2.rs | 17 ++++++----------- crates/zed2/src/main.rs | 3 ++- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 754988a605..e1e79c4d3e 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -207,10 +207,9 @@ pub fn init_settings(cx: &mut AppContext) { ItemSettings::register(cx); } -pub fn init(app_state: Arc, cx: &mut AppContext, call_factory: CallFactory) { +pub fn init(app_state: Arc, cx: &mut AppContext) { init_settings(cx); notifications::init(cx); - cx.set_global(call_factory); // cx.add_global_action({ // let app_state = Arc::downgrade(&app_state); // move |_: &Open, cx: &mut AppContext| { @@ -304,6 +303,7 @@ pub struct AppState { pub user_store: Model, pub workspace_store: Model, pub fs: Arc, + pub call_factory: CallFactory, pub build_window_options: fn(Option, Option, &mut AppContext) -> WindowOptions, pub node_runtime: Arc, @@ -653,7 +653,6 @@ impl Workspace { ]; cx.defer(|this, cx| this.update_window_title(cx)); - let call_factory = cx.global::(); Workspace { window_self: window_handle, weak_self: weak_handle.clone(), @@ -677,7 +676,7 @@ impl Workspace { last_leaders_by_pane: Default::default(), window_edited: false, - call_handler: call_factory(weak_handle.clone(), cx), + call_handler: (app_state.call_factory)(weak_handle.clone(), cx), database_id: workspace_id, app_state, _observe_current_user, @@ -2784,8 +2783,9 @@ impl Workspace { } else { None }; + let room_id = self.call_handler.room_id(cx)?; self.app_state().workspace_store.update(cx, |store, cx| { - store.update_followers(project_id, update, cx) + store.update_followers(project_id, room_id, update, cx) }) } @@ -3825,15 +3825,10 @@ impl WorkspaceStore { pub fn update_followers( &self, project_id: Option, + room_id: u64, update: proto::update_followers::Variant, cx: &AppContext, ) -> Option<()> { - let room_id = self.workspaces.iter().next().and_then(|workspace| { - workspace - .read_with(cx, |this, cx| this.call_handler.room_id(cx)) - .log_err() - .flatten() - })?; let follower_ids: Vec<_> = self .followers .iter() diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 62d337a716..b0a03d8684 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -180,6 +180,7 @@ fn main() { user_store, fs, build_window_options, + call_factory: call::Call::new, // background_actions: todo!("ask Mikayla"), workspace_store, node_runtime, @@ -189,7 +190,7 @@ fn main() { // audio::init(Assets, cx); auto_update::init(http.clone(), client::ZED_SERVER_URL.clone(), cx); - workspace::init(app_state.clone(), cx, call::Call::new); + workspace::init(app_state.clone(), cx); // recent_projects::init(cx); go_to_line::init(cx); From fa74c49dbbccc113e6a8d9f464a04e1193e30c2f Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 22 Nov 2023 19:23:58 +0100 Subject: [PATCH 5/6] Add dummy call handler for tests --- crates/collab2/src/tests/test_server.rs | 1 + crates/workspace2/src/workspace2.rs | 32 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/crates/collab2/src/tests/test_server.rs b/crates/collab2/src/tests/test_server.rs index 090a32d4ca..f620662f71 100644 --- a/crates/collab2/src/tests/test_server.rs +++ b/crates/collab2/src/tests/test_server.rs @@ -221,6 +221,7 @@ impl TestServer { fs: fs.clone(), build_window_options: |_, _, _| Default::default(), node_runtime: FakeNodeRuntime::new(), + call_factory: |_, _| Box::new(workspace::TestCallHandler), }); cx.update(|cx| { diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index e1e79c4d3e..b09b47d24c 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -322,6 +322,36 @@ struct Follower { peer_id: PeerId, } +#[cfg(any(test, feature = "test-support"))] +pub struct TestCallHandler; + +#[cfg(any(test, feature = "test-support"))] +impl CallHandler for TestCallHandler { + fn peer_state(&mut self, id: PeerId, cx: &mut ViewContext) -> Option<(bool, bool)> { + None + } + + fn shared_screen_for_peer( + &self, + peer_id: PeerId, + pane: &View, + cx: &mut ViewContext, + ) -> Option> { + None + } + + fn room_id(&self, cx: &AppContext) -> Option { + None + } + + fn hang_up(&self, cx: AsyncWindowContext) -> Result>> { + anyhow::bail!("TestCallHandler should not be hanging up") + } + + fn active_project(&self, cx: &AppContext) -> Option> { + None + } +} impl AppState { #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &mut AppContext) -> Arc { @@ -352,6 +382,7 @@ impl AppState { workspace_store, node_runtime: FakeNodeRuntime::new(), build_window_options: |_, _, _| Default::default(), + call_factory: |_, _| Box::new(TestCallHandler), }) } } @@ -3298,6 +3329,7 @@ impl Workspace { fs: project.read(cx).fs().clone(), build_window_options: |_, _, _| Default::default(), node_runtime: FakeNodeRuntime::new(), + call_factory: |_, _| Box::new(TestCallHandler), }); let workspace = Self::new(0, project, app_state, cx); workspace.active_pane.update(cx, |pane, cx| pane.focus(cx)); From b45234eecea255cc3fef12b1c5fbf58dba40892e Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 22 Nov 2023 19:24:38 +0100 Subject: [PATCH 6/6] Fix warnings in unimplemented function --- crates/call2/src/call2.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index 18576d4657..9579552d5a 100644 --- a/crates/call2/src/call2.rs +++ b/crates/call2/src/call2.rs @@ -552,14 +552,14 @@ impl CallHandler for Call { fn shared_screen_for_peer( &self, peer_id: PeerId, - pane: &View, + _pane: &View, cx: &mut ViewContext, ) -> Option> { let (call, _) = self.active_call.as_ref()?; let room = call.read(cx).room()?.read(cx); let participant = room.remote_participant_for_peer_id(peer_id)?; - let track = participant.video_tracks.values().next()?.clone(); - let user = participant.user.clone(); + let _track = participant.video_tracks.values().next()?.clone(); + let _user = participant.user.clone(); todo!(); // for item in pane.read(cx).items_of_type::() { // if item.read(cx).peer_id == peer_id {