Achieve end to end channel buffer synchronization

co-authored-by: max <max@zed.dev>
This commit is contained in:
Mikayla 2023-08-22 13:25:31 -07:00
parent 95ea664725
commit 5a0315c4d5
No known key found for this signature in database
10 changed files with 425 additions and 128 deletions

View file

@ -1,7 +1,14 @@
mod channel_store; mod channel_store;
pub mod channel_buffer; pub mod channel_buffer;
use std::sync::Arc;
pub use channel_store::*; pub use channel_store::*;
use client::Client;
#[cfg(test)] #[cfg(test)]
mod channel_store_tests; mod channel_store_tests;
pub fn init(client: &Arc<Client>) {
channel_buffer::init(client);
}

View file

@ -6,30 +6,34 @@ use rpc::{proto, TypedEnvelope};
use std::sync::Arc; use std::sync::Arc;
use util::ResultExt; use util::ResultExt;
// Open the channel document pub(crate) fn init(client: &Arc<Client>) {
// ChannelDocumentView { ChannelDocument, Editor } -> On clone, clones internal ChannelDocument handle, instantiates new editor client.add_model_message_handler(ChannelBuffer::handle_update_channel_buffer);
// Produces a view which is: (ChannelDocument, Editor), ChannelDocument manages subscriptions client.add_model_message_handler(ChannelBuffer::handle_add_channel_buffer_collaborator);
// ChannelDocuments -> Buffers -> Editor with that buffer client.add_model_message_handler(ChannelBuffer::handle_remove_channel_buffer_collaborator);
}
// ChannelDocuments {
// ChannleBuffers: HashMap<bufferId, ModelHandle<language::Buffer>>
// }
type BufferId = u64;
pub struct ChannelBuffer { pub struct ChannelBuffer {
channel_id: ChannelId, channel_id: ChannelId,
buffer_id: BufferId, collaborators: Vec<proto::Collaborator>,
buffer: ModelHandle<language::Buffer>, buffer: ModelHandle<language::Buffer>,
client: Arc<Client>, client: Arc<Client>,
_subscription: client::Subscription,
} }
impl Entity for ChannelBuffer { impl Entity for ChannelBuffer {
type Event = (); type Event = ();
fn release(&mut self, _: &mut AppContext) {
self.client
.send(proto::LeaveChannelBuffer {
channel_id: self.channel_id,
})
.log_err();
}
} }
impl ChannelBuffer { impl ChannelBuffer {
pub fn for_channel( pub fn join_channel(
channel_id: ChannelId, channel_id: ChannelId,
client: Arc<Client>, client: Arc<Client>,
cx: &mut AppContext, cx: &mut AppContext,
@ -45,19 +49,24 @@ impl ChannelBuffer {
.into_iter() .into_iter()
.map(language::proto::deserialize_operation) .map(language::proto::deserialize_operation)
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let buffer_id = response.buffer_id;
let buffer = cx.add_model(|cx| language::Buffer::new(0, base_text, cx)); let collaborators = response.collaborators;
let buffer =
cx.add_model(|cx| language::Buffer::new(response.replica_id as u16, base_text, cx));
buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))?; buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))?;
let subscription = client.subscribe_to_entity(channel_id)?;
anyhow::Ok(cx.add_model(|cx| { anyhow::Ok(cx.add_model(|cx| {
cx.subscribe(&buffer, Self::on_buffer_update).detach(); cx.subscribe(&buffer, Self::on_buffer_update).detach();
client.add_model_message_handler(Self::handle_update_channel_buffer);
Self { Self {
buffer_id,
buffer, buffer,
client, client,
channel_id, channel_id,
collaborators,
_subscription: subscription.set_model(&cx.handle(), &mut cx.to_async()),
} }
})) }))
}) })
@ -77,6 +86,7 @@ impl ChannelBuffer {
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
cx.notify();
this.buffer this.buffer
.update(cx, |buffer, cx| buffer.apply_ops(ops, cx)) .update(cx, |buffer, cx| buffer.apply_ops(ops, cx))
})?; })?;
@ -84,6 +94,49 @@ impl ChannelBuffer {
Ok(()) Ok(())
} }
async fn handle_add_channel_buffer_collaborator(
this: ModelHandle<Self>,
envelope: TypedEnvelope<proto::AddChannelBufferCollaborator>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
let collaborator = envelope.payload.collaborator.ok_or_else(|| {
anyhow::anyhow!(
"Should have gotten a collaborator in the AddChannelBufferCollaborator message"
)
})?;
this.update(&mut cx, |this, cx| {
this.collaborators.push(collaborator);
cx.notify();
});
Ok(())
}
async fn handle_remove_channel_buffer_collaborator(
this: ModelHandle<Self>,
message: TypedEnvelope<proto::RemoveChannelBufferCollaborator>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
this.update(&mut cx, |this, cx| {
this.collaborators.retain(|collaborator| {
if collaborator.peer_id == message.payload.peer_id {
this.buffer.update(cx, |buffer, cx| {
buffer.remove_peer(collaborator.replica_id as u16, cx)
});
false
} else {
true
}
});
cx.notify();
});
Ok(())
}
fn on_buffer_update( fn on_buffer_update(
&mut self, &mut self,
_: ModelHandle<language::Buffer>, _: ModelHandle<language::Buffer>,
@ -94,7 +147,7 @@ impl ChannelBuffer {
let operation = language::proto::serialize_operation(operation); let operation = language::proto::serialize_operation(operation);
self.client self.client
.send(proto::UpdateChannelBuffer { .send(proto::UpdateChannelBuffer {
buffer_id: self.buffer_id, channel_id: self.channel_id,
operations: vec![operation], operations: vec![operation],
}) })
.log_err(); .log_err();
@ -104,4 +157,8 @@ impl ChannelBuffer {
pub fn buffer(&self) -> ModelHandle<language::Buffer> { pub fn buffer(&self) -> ModelHandle<language::Buffer> {
self.buffer.clone() self.buffer.clone()
} }
pub fn collaborators(&self) -> &[proto::Collaborator] {
&self.collaborators
}
} }

View file

@ -11,7 +11,6 @@ impl Database {
self.transaction(|tx| async move { self.transaction(|tx| async move {
let tx = tx; let tx = tx;
// Get or create buffer from channel
self.check_user_is_channel_member(channel_id, user_id, &tx) self.check_user_is_channel_member(channel_id, user_id, &tx)
.await?; .await?;
@ -116,6 +115,7 @@ impl Database {
Ok(proto::JoinChannelBufferResponse { Ok(proto::JoinChannelBufferResponse {
buffer_id: buffer.id.to_proto(), buffer_id: buffer.id.to_proto(),
replica_id: replica_id.to_proto() as u32,
base_text, base_text,
operations, operations,
collaborators: collaborators collaborators: collaborators
@ -137,67 +137,128 @@ impl Database {
connection: ConnectionId, connection: ConnectionId,
) -> Result<Vec<ConnectionId>> { ) -> Result<Vec<ConnectionId>> {
self.transaction(|tx| async move { self.transaction(|tx| async move {
let result = channel_buffer_collaborator::Entity::delete_many() self.leave_channel_buffer_internal(channel_id, connection, &*tx)
.filter( .await
Condition::all()
.add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id))
.add(
channel_buffer_collaborator::Column::ConnectionId
.eq(connection.id as i32),
)
.add(
channel_buffer_collaborator::Column::ConnectionServerId
.eq(connection.owner_id as i32),
),
)
.exec(&*tx)
.await?;
if result.rows_affected == 0 {
Err(anyhow!("not a collaborator on this project"))?;
}
let mut connections = Vec::new();
let mut rows = channel_buffer_collaborator::Entity::find()
.filter(
Condition::all()
.add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id)),
)
.stream(&*tx)
.await?;
while let Some(row) = rows.next().await {
let row = row?;
connections.push(ConnectionId {
id: row.connection_id as u32,
owner_id: row.connection_server_id.0 as u32,
});
}
Ok(connections)
}) })
.await .await
} }
pub async fn leave_channel_buffer_internal(
&self,
channel_id: ChannelId,
connection: ConnectionId,
tx: &DatabaseTransaction,
) -> Result<Vec<ConnectionId>> {
let result = channel_buffer_collaborator::Entity::delete_many()
.filter(
Condition::all()
.add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id))
.add(channel_buffer_collaborator::Column::ConnectionId.eq(connection.id as i32))
.add(
channel_buffer_collaborator::Column::ConnectionServerId
.eq(connection.owner_id as i32),
),
)
.exec(&*tx)
.await?;
if result.rows_affected == 0 {
Err(anyhow!("not a collaborator on this project"))?;
}
let mut connections = Vec::new();
let mut rows = channel_buffer_collaborator::Entity::find()
.filter(
Condition::all().add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id)),
)
.stream(&*tx)
.await?;
while let Some(row) = rows.next().await {
let row = row?;
connections.push(ConnectionId {
id: row.connection_id as u32,
owner_id: row.connection_server_id.0 as u32,
});
}
Ok(connections)
}
pub async fn leave_channel_buffers( pub async fn leave_channel_buffers(
&self, &self,
connection: ConnectionId, connection: ConnectionId,
) -> Result<Option<LeftChannelBuffers>> { ) -> Result<Vec<(ChannelId, Vec<ConnectionId>)>> {
// self.transaction(|tx| async move {
#[derive(Debug, Clone, Copy, EnumIter, DeriveColumn)]
enum QueryChannelIds {
ChannelId,
}
let channel_ids: Vec<ChannelId> = channel_buffer_collaborator::Entity::find()
.select_only()
.column(channel_buffer_collaborator::Column::ChannelId)
.filter(Condition::all().add(
channel_buffer_collaborator::Column::ConnectionId.eq(connection.id as i32),
))
.into_values::<_, QueryChannelIds>()
.all(&*tx)
.await?;
let mut result = Vec::new();
for channel_id in channel_ids {
let collaborators = self
.leave_channel_buffer_internal(channel_id, connection, &*tx)
.await?;
result.push((channel_id, collaborators));
}
Ok(result)
})
.await
} }
pub async fn get_channel_buffer_collaborators(&self, channel_id: ChannelId) -> Result<()> { #[cfg(debug_assertions)]
todo!() pub async fn get_channel_buffer_collaborators(
&self,
channel_id: ChannelId,
) -> Result<Vec<UserId>> {
self.transaction(|tx| async move {
#[derive(Debug, Clone, Copy, EnumIter, DeriveColumn)]
enum QueryUserIds {
UserId,
}
let users: Vec<UserId> = channel_buffer_collaborator::Entity::find()
.select_only()
.column(channel_buffer_collaborator::Column::UserId)
.filter(
Condition::all()
.add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id)),
)
.into_values::<_, QueryUserIds>()
.all(&*tx)
.await?;
Ok(users)
})
.await
} }
pub async fn update_channel_buffer( pub async fn update_channel_buffer(
&self, &self,
buffer_id: BufferId, channel_id: ChannelId,
user: UserId,
operations: &[proto::Operation], operations: &[proto::Operation],
) -> Result<()> { ) -> Result<Vec<ConnectionId>> {
self.transaction(|tx| async move { self.transaction(|tx| async move {
let buffer = buffer::Entity::find_by_id(buffer_id) self.check_user_is_channel_member(channel_id, user, &*tx)
.await?;
let buffer = buffer::Entity::find()
.filter(buffer::Column::ChannelId.eq(channel_id))
.one(&*tx) .one(&*tx)
.await? .await?
.ok_or_else(|| anyhow!("no such buffer"))?; .ok_or_else(|| anyhow!("no such buffer"))?;
let buffer_id = buffer.id;
buffer_operation::Entity::insert_many(operations.iter().filter_map(|operation| { buffer_operation::Entity::insert_many(operations.iter().filter_map(|operation| {
match operation.variant.as_ref()? { match operation.variant.as_ref()? {
proto::operation::Variant::Edit(operation) => { proto::operation::Variant::Edit(operation) => {
@ -237,7 +298,23 @@ impl Database {
.exec(&*tx) .exec(&*tx)
.await?; .await?;
Ok(()) let mut connections = Vec::new();
let mut rows = channel_buffer_collaborator::Entity::find()
.filter(
Condition::all()
.add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id)),
)
.stream(&*tx)
.await?;
while let Some(row) = rows.next().await {
let row = row?;
connections.push(ConnectionId {
id: row.connection_id as u32,
owner_id: row.connection_server_id.0 as u32,
});
}
Ok(connections)
}) })
.await .await
} }

View file

@ -66,11 +66,10 @@ async fn test_channel_buffers(db: &Arc<Database>) {
.unwrap(); .unwrap();
let connection_id_a = ConnectionId { owner_id, id: 1 }; let connection_id_a = ConnectionId { owner_id, id: 1 };
let buffer_response_a = db let _ = db
.join_channel_buffer(zed_id, a_id, connection_id_a) .join_channel_buffer(zed_id, a_id, connection_id_a)
.await .await
.unwrap(); .unwrap();
let buffer_id = BufferId::from_proto(buffer_response_a.buffer_id);
let mut buffer_a = Buffer::new(0, 0, "".to_string()); let mut buffer_a = Buffer::new(0, 0, "".to_string());
let mut operations = Vec::new(); let mut operations = Vec::new();
@ -85,7 +84,7 @@ async fn test_channel_buffers(db: &Arc<Database>) {
.map(|op| proto::serialize_operation(&language::Operation::Buffer(op))) .map(|op| proto::serialize_operation(&language::Operation::Buffer(op)))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
db.update_channel_buffer(buffer_id, &operations) db.update_channel_buffer(zed_id, a_id, &operations)
.await .await
.unwrap(); .unwrap();
@ -115,7 +114,7 @@ async fn test_channel_buffers(db: &Arc<Database>) {
.await .await
.is_err()); .is_err());
//Ensure that both collaborators have shown up // Ensure that both collaborators have shown up
assert_eq!( assert_eq!(
buffer_response_b.collaborators, buffer_response_b.collaborators,
&[ &[
@ -132,6 +131,10 @@ async fn test_channel_buffers(db: &Arc<Database>) {
] ]
); );
// Ensure that get_channel_buffer_collaborators works
let zed_collaborats = db.get_channel_buffer_collaborators(zed_id).await.unwrap();
assert_eq!(zed_collaborats, &[a_id, b_id]);
let collaborators = db let collaborators = db
.leave_channel_buffer(zed_id, connection_id_b) .leave_channel_buffer(zed_id, connection_id_b)
.await .await
@ -139,7 +142,18 @@ async fn test_channel_buffers(db: &Arc<Database>) {
assert_eq!(collaborators, &[connection_id_a],); assert_eq!(collaborators, &[connection_id_a],);
db.connection_lost(connection_id_a).await.unwrap(); let cargo_id = db.create_root_channel("cargo", "2", a_id).await.unwrap();
// assert!() let _ = db
// Test buffer epoch incrementing? .join_channel_buffer(cargo_id, a_id, connection_id_a)
.await
.unwrap();
db.leave_channel_buffers(connection_id_a).await.unwrap();
let zed_collaborators = db.get_channel_buffer_collaborators(zed_id).await.unwrap();
let cargo_collaborators = db.get_channel_buffer_collaborators(cargo_id).await.unwrap();
assert_eq!(zed_collaborators, &[]);
assert_eq!(cargo_collaborators, &[]);
// TODO: test buffer epoch incrementing
} }

View file

@ -2,10 +2,7 @@ mod connection_pool;
use crate::{ use crate::{
auth, auth,
db::{ db::{self, ChannelId, ChannelsForUser, Database, ProjectId, RoomId, ServerId, User, UserId},
self, BufferId, ChannelId, ChannelsForUser, Database, ProjectId, RoomId, ServerId, User,
UserId,
},
executor::Executor, executor::Executor,
AppState, Result, AppState, Result,
}; };
@ -38,8 +35,8 @@ use lazy_static::lazy_static;
use prometheus::{register_int_gauge, IntGauge}; use prometheus::{register_int_gauge, IntGauge};
use rpc::{ use rpc::{
proto::{ proto::{
self, Ack, AnyTypedEnvelope, EntityMessage, EnvelopedMessage, LiveKitConnectionInfo, self, Ack, AddChannelBufferCollaborator, AnyTypedEnvelope, EntityMessage, EnvelopedMessage,
RequestMessage, LiveKitConnectionInfo, RequestMessage,
}, },
Connection, ConnectionId, Peer, Receipt, TypedEnvelope, Connection, ConnectionId, Peer, Receipt, TypedEnvelope,
}; };
@ -860,6 +857,7 @@ async fn connection_lost(
futures::select_biased! { futures::select_biased! {
_ = executor.sleep(RECONNECT_TIMEOUT).fuse() => { _ = executor.sleep(RECONNECT_TIMEOUT).fuse() => {
leave_room_for_session(&session).await.trace_err(); leave_room_for_session(&session).await.trace_err();
leave_channel_buffers_for_session(&session).await.trace_err();
if !session if !session
.connection_pool() .connection_pool()
@ -872,6 +870,8 @@ async fn connection_lost(
} }
} }
update_user_contacts(session.user_id, &session).await?; update_user_contacts(session.user_id, &session).await?;
} }
_ = teardown.changed().fuse() => {} _ = teardown.changed().fuse() => {}
} }
@ -2496,8 +2496,51 @@ async fn join_channel_buffer(
.join_channel_buffer(channel_id, session.user_id, session.connection_id) .join_channel_buffer(channel_id, session.user_id, session.connection_id)
.await?; .await?;
let replica_id = open_response.replica_id;
let collaborators = open_response.collaborators.clone();
response.send(open_response)?; response.send(open_response)?;
let update = AddChannelBufferCollaborator {
channel_id: channel_id.to_proto(),
collaborator: Some(proto::Collaborator {
user_id: session.user_id.to_proto(),
peer_id: Some(session.connection_id.into()),
replica_id,
}),
};
channel_buffer_updated(
session.connection_id,
collaborators
.iter()
.filter_map(|collaborator| Some(collaborator.peer_id?.into())),
&update,
&session.peer,
);
Ok(())
}
async fn update_channel_buffer(
request: proto::UpdateChannelBuffer,
session: Session,
) -> Result<()> {
let db = session.db().await;
let channel_id = ChannelId::from_proto(request.channel_id);
let collaborators = db
.update_channel_buffer(channel_id, session.user_id, &request.operations)
.await?;
channel_buffer_updated(
session.connection_id,
collaborators,
&proto::UpdateChannelBuffer {
channel_id: channel_id.to_proto(),
operations: request.operations,
},
&session.peer,
);
Ok(()) Ok(())
} }
@ -2515,18 +2558,28 @@ async fn leave_channel_buffer(
response.send(Ack {})?; response.send(Ack {})?;
channel_buffer_updated(
session.connection_id,
collaborators_to_notify,
&proto::RemoveChannelBufferCollaborator {
channel_id: channel_id.to_proto(),
peer_id: Some(session.connection_id.into()),
},
&session.peer,
);
Ok(()) Ok(())
} }
async fn update_channel_buffer( fn channel_buffer_updated<T: EnvelopedMessage>(
request: proto::UpdateChannelBuffer, sender_id: ConnectionId,
session: Session, collaborators: impl IntoIterator<Item = ConnectionId>,
) -> Result<()> { message: &T,
let db = session.db().await; peer: &Peer,
) {
// TODO: Broadcast to buffer members broadcast(Some(sender_id), collaborators.into_iter(), |peer_id| {
peer.send(peer_id.into(), message.clone())
Ok(()) });
} }
async fn update_diff_base(request: proto::UpdateDiffBase, session: Session) -> Result<()> { async fn update_diff_base(request: proto::UpdateDiffBase, session: Session) -> Result<()> {
@ -2854,6 +2907,28 @@ async fn leave_room_for_session(session: &Session) -> Result<()> {
Ok(()) Ok(())
} }
async fn leave_channel_buffers_for_session(session: &Session) -> Result<()> {
let left_channel_buffers = session
.db()
.await
.leave_channel_buffers(session.connection_id)
.await?;
for (channel_id, connections) in left_channel_buffers {
channel_buffer_updated(
session.connection_id,
connections,
&proto::RemoveChannelBufferCollaborator {
channel_id: channel_id.to_proto(),
peer_id: Some(session.connection_id.into()),
},
&session.peer,
);
}
Ok(())
}
fn project_left(project: &db::LeftProject, session: &Session) { fn project_left(project: &db::LeftProject, session: &Session) {
for connection_id in &project.connection_ids { for connection_id in &project.connection_ids {
if project.host_user_id == session.user_id { if project.host_user_id == session.user_id {

View file

@ -211,6 +211,7 @@ impl TestServer {
workspace::init(app_state.clone(), cx); workspace::init(app_state.clone(), cx);
audio::init((), cx); audio::init((), cx);
call::init(client.clone(), user_store.clone(), cx); call::init(client.clone(), user_store.clone(), cx);
channel::init(&client);
}); });
client client

View file

@ -1,11 +1,13 @@
use crate::tests::TestServer; use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
use channel::channel_buffer::ChannelBuffer; use channel::channel_buffer::ChannelBuffer;
use client::UserId;
use gpui::{executor::Deterministic, ModelHandle, TestAppContext}; use gpui::{executor::Deterministic, ModelHandle, TestAppContext};
use std::{ops::Range, sync::Arc}; use rpc::{proto, RECEIVE_TIMEOUT};
use std::sync::Arc;
#[gpui::test] #[gpui::test]
async fn test_channel_buffers( async fn test_core_channel_buffers(
deterministic: Arc<Deterministic>, deterministic: Arc<Deterministic>,
cx_a: &mut TestAppContext, cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext, cx_b: &mut TestAppContext,
@ -19,60 +21,103 @@ async fn test_channel_buffers(
.make_channel("zed", (&client_a, cx_a), &mut [(&client_b, cx_b)]) .make_channel("zed", (&client_a, cx_a), &mut [(&client_b, cx_b)])
.await; .await;
// Client A joins the channel buffer
let channel_buffer_a = cx_a let channel_buffer_a = cx_a
.update(|cx| ChannelBuffer::for_channel(zed_id, client_a.client().to_owned(), cx)) .update(|cx| ChannelBuffer::join_channel(zed_id, client_a.client().to_owned(), cx))
.await .await
.unwrap(); .unwrap();
// Client A edits the buffer
let buffer_a = channel_buffer_a.read_with(cx_a, |buffer, _| buffer.buffer()); let buffer_a = channel_buffer_a.read_with(cx_a, |buffer, _| buffer.buffer());
edit_channel_buffer(&buffer_a, cx_a, [(0..0, "hello world")]); buffer_a.update(cx_a, |buffer, cx| {
edit_channel_buffer(&buffer_a, cx_a, [(5..5, ", cruel")]); buffer.edit([(0..0, "hello world")], None, cx)
edit_channel_buffer(&buffer_a, cx_a, [(0..5, "goodbye")]); });
undo_channel_buffer(&buffer_a, cx_a); buffer_a.update(cx_a, |buffer, cx| {
buffer.edit([(5..5, ", cruel")], None, cx)
});
buffer_a.update(cx_a, |buffer, cx| {
buffer.edit([(0..5, "goodbye")], None, cx)
});
buffer_a.update(cx_a, |buffer, cx| buffer.undo(cx));
deterministic.run_until_parked();
assert_eq!(channel_buffer_text(&buffer_a, cx_a), "hello, cruel world"); assert_eq!(buffer_text(&buffer_a, cx_a), "hello, cruel world");
// Client B joins the channel buffer
let channel_buffer_b = cx_b let channel_buffer_b = cx_b
.update(|cx| ChannelBuffer::for_channel(zed_id, client_b.client().to_owned(), cx)) .update(|cx| ChannelBuffer::join_channel(zed_id, client_b.client().to_owned(), cx))
.await .await
.unwrap(); .unwrap();
channel_buffer_b.read_with(cx_b, |buffer, _| {
assert_collaborators(
buffer.collaborators(),
&[client_a.user_id(), client_b.user_id()],
);
});
// Client B sees the correct text, and then edits it
let buffer_b = channel_buffer_b.read_with(cx_b, |buffer, _| buffer.buffer()); let buffer_b = channel_buffer_b.read_with(cx_b, |buffer, _| buffer.buffer());
assert_eq!(buffer_text(&buffer_b, cx_b), "hello, cruel world");
buffer_b.update(cx_b, |buffer, cx| {
buffer.edit([(7..12, "beautiful")], None, cx)
});
assert_eq!(channel_buffer_text(&buffer_b, cx_b), "hello, cruel world"); // Both A and B see the new edit
deterministic.run_until_parked();
edit_channel_buffer(&buffer_b, cx_b, [(7..12, "beautiful")]); assert_eq!(buffer_text(&buffer_a, cx_a), "hello, beautiful world");
assert_eq!(buffer_text(&buffer_b, cx_b), "hello, beautiful world");
// Client A closes the channel buffer.
cx_a.update(|_| drop(channel_buffer_a));
deterministic.run_until_parked(); deterministic.run_until_parked();
// Client B sees that client A is gone from the channel buffer.
channel_buffer_b.read_with(cx_b, |buffer, _| {
assert_collaborators(&buffer.collaborators(), &[client_b.user_id()]);
});
// Client A rejoins the channel buffer
let _channel_buffer_a = cx_a
.update(|cx| ChannelBuffer::join_channel(zed_id, client_a.client().to_owned(), cx))
.await
.unwrap();
deterministic.run_until_parked();
// Sanity test, make sure we saw A rejoining
channel_buffer_b.read_with(cx_b, |buffer, _| {
assert_collaborators(
&buffer.collaborators(),
&[client_b.user_id(), client_a.user_id()],
);
});
// Client A loses connection.
server.forbid_connections();
server.disconnect_client(client_a.peer_id().unwrap());
deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
// Client B observes A disconnect
channel_buffer_b.read_with(cx_b, |buffer, _| {
assert_collaborators(&buffer.collaborators(), &[client_b.user_id()]);
});
// TODO:
// - Test synchronizing offline updates, what happens to A's channel buffer?
}
#[track_caller]
fn assert_collaborators(collaborators: &[proto::Collaborator], ids: &[Option<UserId>]) {
assert_eq!( assert_eq!(
channel_buffer_text(&buffer_a, cx_a), collaborators
"hello, beautiful world" .into_iter()
); .map(|collaborator| collaborator.user_id)
assert_eq!( .collect::<Vec<_>>(),
channel_buffer_text(&buffer_b, cx_b), ids.into_iter().map(|id| id.unwrap()).collect::<Vec<_>>()
"hello, beautiful world"
); );
} }
fn edit_channel_buffer<I>( fn buffer_text(channel_buffer: &ModelHandle<language::Buffer>, cx: &mut TestAppContext) -> String {
channel_buffer: &ModelHandle<language::Buffer>,
cx: &mut TestAppContext,
edits: I,
) where
I: IntoIterator<Item = (Range<usize>, &'static str)>,
{
channel_buffer.update(cx, |buffer, cx| buffer.edit(edits, None, cx));
}
fn undo_channel_buffer(channel_buffer: &ModelHandle<language::Buffer>, cx: &mut TestAppContext) {
channel_buffer.update(cx, |buffer, cx| buffer.undo(cx));
}
fn channel_buffer_text(
channel_buffer: &ModelHandle<language::Buffer>,
cx: &mut TestAppContext,
) -> String {
channel_buffer.read_with(cx, |buffer, _| buffer.text()) channel_buffer.read_with(cx, |buffer, _| buffer.text())
} }

View file

@ -147,6 +147,8 @@ message Envelope {
JoinChannelBufferResponse join_channel_buffer_response = 132; JoinChannelBufferResponse join_channel_buffer_response = 132;
UpdateChannelBuffer update_channel_buffer = 133; UpdateChannelBuffer update_channel_buffer = 133;
LeaveChannelBuffer leave_channel_buffer = 134; LeaveChannelBuffer leave_channel_buffer = 134;
AddChannelBufferCollaborator add_channel_buffer_collaborator = 135;
RemoveChannelBufferCollaborator remove_channel_buffer_collaborator = 136;
} }
} }
@ -416,6 +418,16 @@ message RemoveProjectCollaborator {
PeerId peer_id = 2; PeerId peer_id = 2;
} }
message AddChannelBufferCollaborator {
uint64 channel_id = 1;
Collaborator collaborator = 2;
}
message RemoveChannelBufferCollaborator {
uint64 channel_id = 1;
PeerId peer_id = 2;
}
message GetDefinition { message GetDefinition {
uint64 project_id = 1; uint64 project_id = 1;
uint64 buffer_id = 2; uint64 buffer_id = 2;
@ -546,8 +558,8 @@ message UpdateBuffer {
} }
message UpdateChannelBuffer { message UpdateChannelBuffer {
uint64 buffer_id = 2; uint64 channel_id = 1;
repeated Operation operations = 3; repeated Operation operations = 2;
} }
message UpdateBufferFile { message UpdateBufferFile {
@ -964,9 +976,10 @@ message JoinChannelBuffer {
message JoinChannelBufferResponse { message JoinChannelBufferResponse {
uint64 buffer_id = 1; uint64 buffer_id = 1;
string base_text = 2; uint32 replica_id = 2;
repeated Operation operations = 3; string base_text = 3;
repeated Collaborator collaborators = 4; repeated Operation operations = 4;
repeated Collaborator collaborators = 5;
} }
message LeaveChannelBuffer { message LeaveChannelBuffer {

View file

@ -252,7 +252,9 @@ messages!(
(JoinChannelBuffer, Foreground), (JoinChannelBuffer, Foreground),
(JoinChannelBufferResponse, Foreground), (JoinChannelBufferResponse, Foreground),
(LeaveChannelBuffer, Background), (LeaveChannelBuffer, Background),
(UpdateChannelBuffer, Foreground) (UpdateChannelBuffer, Foreground),
(RemoveChannelBufferCollaborator, Foreground),
(AddChannelBufferCollaborator, Foreground),
); );
request_messages!( request_messages!(
@ -376,7 +378,12 @@ entity_messages!(
UpdateDiffBase UpdateDiffBase
); );
entity_messages!(buffer_id, UpdateChannelBuffer); entity_messages!(
channel_id,
UpdateChannelBuffer,
RemoveChannelBufferCollaborator,
AddChannelBufferCollaborator
);
const KIB: usize = 1024; const KIB: usize = 1024;
const MIB: usize = KIB * 1024; const MIB: usize = KIB * 1024;

View file

@ -158,6 +158,7 @@ fn main() {
outline::init(cx); outline::init(cx);
project_symbols::init(cx); project_symbols::init(cx);
project_panel::init(Assets, cx); project_panel::init(Assets, cx);
channel::init(&client);
diagnostics::init(cx); diagnostics::init(cx);
search::init(cx); search::init(cx);
semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx); semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx);