1- use std:: os:: windows:: process;
1+ // use std::os::windows::process;
22use std:: { sync:: Arc } ;
33use futures_util:: stream:: SplitSink ;
44use futures_util:: { SinkExt , StreamExt } ;
55use tokio:: net:: { TcpListener , TcpStream } ;
66use tokio:: sync:: Mutex ;
7+ use tokio:: sync:: watch;
78use tokio_tungstenite:: {
89 accept_async,
910 tungstenite:: { Message } ,
@@ -12,10 +13,11 @@ use tokio_tungstenite::{
1213use tokio_util:: sync:: CancellationToken ;
1314use shared_logic:: bc:: { start_broadcast} ;
1415use shared_logic:: db:: { initialize_connection} ;
15- use shared_logic:: lsl:: { ProcessingConfig } ; // get ProcessingConfig from lsl.rs
16+ use shared_logic:: lsl:: { ProcessingConfig , WindowingConfig } ; // get ProcessingConfig from lsl.rs
1617use dotenvy:: dotenv;
1718use log:: { info, error} ;
1819use serde_json; // used to parse ProcessingConfig from JSON sent by frontend
20+ use serde_json:: Value ; // used to parse ProcessingConfig from JSON sent by frontend
1921
2022
2123#[ tokio:: main]
@@ -75,83 +77,128 @@ async fn handle_ws(stream: TcpStream) {
7577 }
7678}
7779
78- // handle_connection, starts a async broadcast task,
79- // then listens for incoming websocket closing request with the read stream in order to stop the broadcast task.
8080async fn handle_connection ( ws_stream : WebSocketStream < TcpStream > ) {
81- let ( write, mut read) = ws_stream. split ( ) ;
82- // set up for the broadcast task
83- let write = Arc :: new ( Mutex :: new ( write) ) ;
81+ let ( write, mut read) = ws_stream. split ( ) ;
82+ let write = Arc :: new ( Mutex :: new ( write) ) ;
8483 let write_clone = write. clone ( ) ;
8584 let cancel_token = CancellationToken :: new ( ) ;
8685 let cancel_clone = cancel_token. clone ( ) ;
8786
88- // setup registration for signal processing configuration
89- let signal_config = read . next ( ) . await ;
87+ let mut processing_config = ProcessingConfig :: default ( ) ;
88+ let mut initial_windowing = WindowingConfig :: default ( ) ;
9089
91- // we have the ProcessingConfig struct
92- // check if we received a message (two layers of unwrapping needed)
93- let processing_config: ProcessingConfig = match signal_config {
90+ // Give the frontend a short window to send configs before we start
91+ // Use a timeout so we don't block forever if only one config arrives
92+ let config_timeout = tokio:: time:: Duration :: from_millis ( 500 ) ;
93+ let deadline = tokio:: time:: Instant :: now ( ) + config_timeout;
9494
95- Some ( Ok ( config_json) ) => {
96-
97- // here, we parse the json into a signal config struct using serde_json
98- let config_text = config_json. to_text ( ) . unwrap ( ) ;
95+ loop {
96+ let remaining = deadline. saturating_duration_since ( tokio:: time:: Instant :: now ( ) ) ;
97+ if remaining. is_zero ( ) {
98+ info ! ( "Config window elapsed, starting with current configs." ) ;
99+ break ;
100+ }
99101
100- match serde_json:: from_str ( config_text) {
101- Ok ( config) => config,
102- Err ( e) => {
103- error ! ( "Error parsing signal configuration JSON: {}" , e) ;
102+ match tokio:: time:: timeout ( remaining, read. next ( ) ) . await {
103+ Ok ( Some ( Ok ( msg) ) ) if msg. is_text ( ) => {
104+ let text = msg. to_text ( ) . unwrap ( ) ;
105+ if text == "clientClosing" {
106+ info ! ( "Client closing during config phase." ) ;
104107 return ;
105108 }
109+ match parse_config_message ( text) {
110+ ConfigMessage :: Processing ( cfg) => {
111+ info ! ( "Received ProcessingConfig" ) ;
112+ processing_config = cfg;
113+ }
114+ ConfigMessage :: Windowing ( cfg) => {
115+ info ! ( "Received WindowingConfig: chunk={}, overlap={}" , cfg. chunk_size, cfg. overlap_size) ;
116+ initial_windowing = cfg;
117+ }
118+ ConfigMessage :: Unknown => {
119+ info ! ( "Non-config message during config phase: {}" , text) ;
120+ break ;
121+ }
122+ }
106123 }
107-
124+ Ok ( Some ( Err ( e) ) ) => { error ! ( "Error receiving config: {}" , e) ; return ; }
125+ Ok ( None ) => { error ! ( "Connection closed during config phase." ) ; return ; }
126+ Err ( _) => {
127+ // Timeout elapsed
128+ info ! ( "Config timeout, starting with received configs." ) ;
129+ break ;
130+ }
131+ Ok ( _) => { }
108132 }
133+ }
109134
110- Some ( Err ( e) ) => {
111- error ! ( "Error receiving signal configuration: {}" , e) ;
112- return ;
113- }
135+ info ! ( "Starting broadcast with chunk={}, overlap={}" , initial_windowing. chunk_size, initial_windowing. overlap_size) ;
114136
115- None => {
116- error ! ( "No signal configuration received from client. Closing connection." ) ;
117- return ;
118- }
119- } ;
137+ let ( windowing_tx, windowing_rx) = watch:: channel ( initial_windowing) ;
120138
121- // spawns the broadcast task
122139 let mut broadcast = Some ( tokio:: spawn ( async move {
123- // pass ProcessingConfig into broadcast so it reaches receive_eeg
124- start_broadcast ( write_clone, cancel_clone, processing_config) . await ;
140+ start_broadcast ( write_clone, cancel_clone, processing_config, windowing_rx) . await ;
125141 } ) ) ;
126142
127-
128- //listens for incoming messages
129143 while let Some ( msg) = read. next ( ) . await {
130144 match msg {
131- Ok ( msg) if msg. is_text ( ) => { //prep for closing, this currently will not be called, waiting for frontend
145+ Ok ( msg) if msg. is_text ( ) => {
132146 let text = msg. to_text ( ) . unwrap ( ) ;
133- info ! ( "Received request: {}" , text) ;
134147 if text == "clientClosing" {
135- handle_prep_close ( & mut broadcast, & cancel_token, & write. clone ( ) ) . await ;
148+ handle_prep_close ( & mut broadcast, & cancel_token, & write) . await ;
149+ break ;
150+ }
151+ match parse_config_message ( text) {
152+ ConfigMessage :: Windowing ( cfg) => {
153+ info ! ( "Windowing update: chunk={}, overlap={}" , cfg. chunk_size, cfg. overlap_size) ;
154+ let _ = windowing_tx. send ( cfg) ;
155+ }
156+ ConfigMessage :: Processing ( _) => {
157+ info ! ( "Processing config update received" ) ;
158+ }
159+ ConfigMessage :: Unknown => {
160+ error ! ( "Unknown mid-stream message: {}" , text) ;
161+ }
136162 }
137163 }
138- Ok ( Message :: Close ( frame) ) => { //handles closing.
139- info ! ( "Received a close request from the client" ) ;
140- // cancel_token.cancel(); // remove after frontend updates
141- let mut write = write. lock ( ) . await ;
142- let _ = write. send ( Message :: Close ( frame) ) . await ;
164+ Ok ( Message :: Close ( frame) ) => {
165+ let mut w = write. lock ( ) . await ;
166+ let _ = w. send ( Message :: Close ( frame) ) . await ;
143167 break ;
144168 }
145169 Ok ( _) => continue ,
146- Err ( e) => {
147- error ! ( "Read error (client likely disconnected): {}" , e) ;
148- break ;
149- }
170+ Err ( e) => { error ! ( "Read error: {}" , e) ; break ; }
150171 }
151172 }
152173 info ! ( "Client disconnected." ) ;
153174}
154175
176+ // Discriminate config type by which fields are present
177+ enum ConfigMessage {
178+ Processing ( ProcessingConfig ) ,
179+ Windowing ( WindowingConfig ) ,
180+ Unknown ,
181+ }
182+
183+ fn parse_config_message ( text : & str ) -> ConfigMessage {
184+ let Ok ( value) = serde_json:: from_str :: < Value > ( text) else {
185+ return ConfigMessage :: Unknown ;
186+ } ;
187+ if value. get ( "chunk_size" ) . is_some ( ) {
188+ match serde_json:: from_value :: < WindowingConfig > ( value) {
189+ Ok ( cfg) => ConfigMessage :: Windowing ( cfg) ,
190+ Err ( e) => { error ! ( "Failed to parse WindowingConfig: {}" , e) ; ConfigMessage :: Unknown }
191+ }
192+ } else if value. get ( "apply_bandpass" ) . is_some ( ) {
193+ match serde_json:: from_value :: < ProcessingConfig > ( value) {
194+ Ok ( cfg) => ConfigMessage :: Processing ( cfg) ,
195+ Err ( e) => { error ! ( "Failed to parse ProcessingConfig: {}" , e) ; ConfigMessage :: Unknown }
196+ }
197+ } else {
198+ ConfigMessage :: Unknown
199+ }
200+ }
201+
155202// handle_prep_close uses the cancel_token to stop the broadcast sender task, and sends a "prep close complete" message to the client
156203async fn handle_prep_close (
157204 broadcast_task : & mut Option < tokio:: task:: JoinHandle < ( ) > > ,
0 commit comments