@@ -60,14 +60,47 @@ impl Default for SyncServerConfig {
6060#[ derive( Debug , Clone , serde:: Serialize ) ]
6161#[ serde( tag = "type" , content = "payload" ) ]
6262pub enum BroadcastMessage {
63- /// Full graph snapshot or delta update.
63+ /// Initial handshake with server info
64+ Hello ( HelloPayload ) ,
65+ /// Start of a graph stream
66+ GraphBegin ( GraphBeginPayload ) ,
67+ /// Batch of nodes
68+ NodeBatch ( NodeBatchPayload ) ,
69+ /// Batch of edges
70+ EdgeBatch ( EdgeBatchPayload ) ,
71+ /// End of graph stream
72+ GraphEnd ,
73+ /// Full graph snapshot or delta update (Legacy/Incremental)
6474 GraphUpdate ( GraphUpdatePayload ) ,
6575 /// Tell the visualizer to focus on a specific node.
6676 FocusNode ( FocusNodePayload ) ,
6777 /// Indexer progress status.
6878 IndexerStatus ( IndexerStatusPayload ) ,
6979}
7080
81+ #[ derive( Debug , Clone , serde:: Serialize ) ]
82+ pub struct HelloPayload {
83+ pub version : String ,
84+ pub node_count : usize ,
85+ pub edge_count : usize ,
86+ }
87+
88+ #[ derive( Debug , Clone , serde:: Serialize ) ]
89+ pub struct GraphBeginPayload {
90+ pub total_nodes : usize ,
91+ pub total_edges : usize ,
92+ }
93+
94+ #[ derive( Debug , Clone , serde:: Serialize ) ]
95+ pub struct NodeBatchPayload {
96+ pub nodes : Vec < arbor_core:: CodeNode > ,
97+ }
98+
99+ #[ derive( Debug , Clone , serde:: Serialize ) ]
100+ pub struct EdgeBatchPayload {
101+ pub edges : Vec < arbor_graph:: GraphEdge > ,
102+ }
103+
71104#[ derive( Debug , Clone , serde:: Serialize ) ]
72105pub struct GraphUpdatePayload {
73106 /// Whether this is a full snapshot or delta.
@@ -313,32 +346,111 @@ async fn handle_client(
313346 graph : SharedGraph ,
314347 mut broadcast_rx : broadcast:: Receiver < BroadcastMessage > ,
315348) -> Result < ( ) , Box < dyn std:: error:: Error + Send + Sync > > {
316- let ws_stream = accept_async ( stream) . await ?;
349+ use tokio_tungstenite:: tungstenite:: protocol:: WebSocketConfig ;
350+
351+ let config = WebSocketConfig {
352+ max_send_queue : None ,
353+ max_message_size : Some ( 64 * 1024 * 1024 ) , // 64 MB
354+ max_frame_size : Some ( 64 * 1024 * 1024 ) , // 64 MB
355+ accept_unmasked_frames : false ,
356+ ..Default :: default ( )
357+ } ;
358+
359+ let ws_stream = tokio_tungstenite:: accept_async_with_config ( stream, Some ( config) ) . await ?;
317360 let ( mut write, mut read) = ws_stream. split ( ) ;
318361
319362 info ! ( "✅ WebSocket handshake complete with {}" , addr) ;
320363
321- // Send initial graph snapshot
322- {
364+ // 1. Send Hello (Metadata)
365+ let ( node_count , edge_count , nodes , edges ) = {
323366 let g = graph. read ( ) . await ;
324- let snapshot = BroadcastMessage :: GraphUpdate ( GraphUpdatePayload {
325- is_delta : false ,
326- node_count : g. node_count ( ) ,
327- edge_count : g. edge_count ( ) ,
328- file_count : g. stats ( ) . files ,
329- changed_files : vec ! [ ] ,
330- timestamp : std:: time:: SystemTime :: now ( )
331- . duration_since ( std:: time:: UNIX_EPOCH )
332- . unwrap ( )
333- . as_secs ( ) ,
334- nodes : Some ( g. nodes ( ) . cloned ( ) . collect ( ) ) ,
335- edges : Some ( g. export_edges ( ) ) ,
367+ (
368+ g. node_count ( ) ,
369+ g. edge_count ( ) ,
370+ g. nodes ( ) . cloned ( ) . collect :: < Vec < _ > > ( ) ,
371+ g. export_edges ( ) ,
372+ )
373+ } ;
374+
375+ let hello = BroadcastMessage :: Hello ( HelloPayload {
376+ version : "1.1.1" . to_string ( ) ,
377+ node_count,
378+ edge_count,
379+ } ) ;
380+
381+ let json = serde_json:: to_string ( & hello) ?;
382+ write. send ( Message :: Text ( json) ) . await ?;
383+ info ! (
384+ "👋 Sent Hello ({} nodes, {} edges) to {}" ,
385+ node_count, edge_count, addr
386+ ) ;
387+
388+ // 2. Wait for Client Ready
389+ info ! ( "⏳ Waiting for client {} to be ready..." , addr) ;
390+ let mut ready = false ;
391+ while let Some ( msg) = read. next ( ) . await {
392+ match msg {
393+ Ok ( Message :: Text ( text) ) => {
394+ // Simple parsing for "ready_for_graph"
395+ if text. contains ( "ready_for_graph" ) {
396+ ready = true ;
397+ info ! ( "✅ Client {} is ready for graph" , addr) ;
398+ break ;
399+ }
400+ debug ! ( "Running pre-ready protocol with {}: {}" , addr, text) ;
401+ }
402+ Ok ( Message :: Ping ( data) ) => {
403+ write. send ( Message :: Pong ( data) ) . await ?;
404+ }
405+ Ok ( Message :: Close ( _) ) => return Ok ( ( ) ) ,
406+ Err ( e) => return Err ( e. into ( ) ) ,
407+ _ => { }
408+ }
409+ }
410+
411+ if !ready {
412+ warn ! ( "Client {} disconnected before sending ready signal" , addr) ;
413+ return Ok ( ( ) ) ;
414+ }
415+
416+ // 3. Stream Graph (Chunked)
417+ let begin = BroadcastMessage :: GraphBegin ( GraphBeginPayload {
418+ total_nodes : node_count,
419+ total_edges : edge_count,
420+ } ) ;
421+ write
422+ . send ( Message :: Text ( serde_json:: to_string ( & begin) ?) )
423+ . await ?;
424+
425+ // Stream Nodes
426+ for chunk in nodes. chunks ( 50 ) {
427+ let batch = BroadcastMessage :: NodeBatch ( NodeBatchPayload {
428+ nodes : chunk. to_vec ( ) ,
336429 } ) ;
430+ write
431+ . send ( Message :: Text ( serde_json:: to_string ( & batch) ?) )
432+ . await ?;
433+ }
434+ info ! ( "📤 Streamed {} nodes to {}" , node_count, addr) ;
337435
338- let json = serde_json:: to_string ( & snapshot) ?;
339- write. send ( Message :: Text ( json) ) . await ?;
340- debug ! ( "📤 Sent initial snapshot to {}" , addr) ;
436+ // Stream Edges
437+ for chunk in edges. chunks ( 100 ) {
438+ let batch = BroadcastMessage :: EdgeBatch ( EdgeBatchPayload {
439+ edges : chunk. to_vec ( ) ,
440+ } ) ;
441+ write
442+ . send ( Message :: Text ( serde_json:: to_string ( & batch) ?) )
443+ . await ?;
341444 }
445+ info ! ( "📤 Streamed {} edges to {}" , edge_count, addr) ;
446+
447+ // End Stream
448+ write
449+ . send ( Message :: Text ( serde_json:: to_string (
450+ & BroadcastMessage :: GraphEnd ,
451+ ) ?) )
452+ . await ?;
453+ info ! ( "🏁 Graph stream complete for {}" , addr) ;
342454
343455 // Two-way message handling
344456 loop {
0 commit comments