|
|
@ -7,24 +7,26 @@ use std::thread; |
|
|
use clap::{App, Arg};
|
|
|
use clap::{App, Arg};
|
|
|
use crossbeam_channel;
|
|
|
use crossbeam_channel;
|
|
|
use env_logger;
|
|
|
use env_logger;
|
|
|
|
|
|
use futures::stream::{Stream, StreamExt};
|
|
|
|
|
|
use log::info;
|
|
|
use solstice_proto;
|
|
|
use solstice_proto;
|
|
|
|
|
|
use tokio::net::TcpStream;
|
|
|
|
|
|
|
|
|
mod client;
|
|
|
mod client;
|
|
|
#[cfg(test)]
|
|
|
|
|
|
mod context;
|
|
|
mod context;
|
|
|
mod control;
|
|
|
mod control;
|
|
|
#[cfg(test)]
|
|
|
|
|
|
mod dispatcher;
|
|
|
mod dispatcher;
|
|
|
#[cfg(test)]
|
|
|
|
|
|
mod executor;
|
|
|
mod executor;
|
|
|
#[cfg(test)]
|
|
|
|
|
|
mod handlers;
|
|
|
mod handlers;
|
|
|
mod login;
|
|
|
mod login;
|
|
|
#[cfg(test)]
|
|
|
|
|
|
mod message_handler;
|
|
|
mod message_handler;
|
|
|
mod room;
|
|
|
mod room;
|
|
|
mod user;
|
|
|
mod user;
|
|
|
|
|
|
|
|
|
|
|
|
use context::Context;
|
|
|
|
|
|
use dispatcher::Dispatcher;
|
|
|
|
|
|
use executor::Executor;
|
|
|
|
|
|
|
|
|
fn old_main() {
|
|
|
fn old_main() {
|
|
|
let (proto_to_client_tx, proto_to_client_rx) = crossbeam_channel::unbounded();
|
|
|
let (proto_to_client_tx, proto_to_client_rx) = crossbeam_channel::unbounded();
|
|
|
|
|
|
|
|
|
@ -51,11 +53,95 @@ fn old_main() { |
|
|
client.run();
|
|
|
client.run();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
fn async_main() {
|
|
|
|
|
|
// TODO
|
|
|
|
|
|
|
|
|
// There is a risk of deadlock if we use two bounded channels here:
|
|
|
|
|
|
//
|
|
|
|
|
|
// - client task is blocked trying to send response to dispatcher
|
|
|
|
|
|
// - all dispatcher threads are blocked trying to send requests to client
|
|
|
|
|
|
//
|
|
|
|
|
|
// This stems from the fact that requests are only read from the channel and
|
|
|
|
|
|
// sent to the server when `inbound` is being polled, which is mutually
|
|
|
|
|
|
// exclusive with `dispatcher_tx.send()` being polled.
|
|
|
|
|
|
//
|
|
|
|
|
|
// This could be fixed in one of two ways, at least:
|
|
|
|
|
|
//
|
|
|
|
|
|
// - write `Client` interface in terms of channels, not streams
|
|
|
|
|
|
// - this would allow both receiving and sending tasks to run concurrently
|
|
|
|
|
|
// inside `Client`
|
|
|
|
|
|
// - in other words, sending the response on the dispatcher channel would
|
|
|
|
|
|
// run concurrently with sending requests to the server
|
|
|
|
|
|
// - use `FrameReader` / `FrameWriter` directly instead, and synchronize their
|
|
|
|
|
|
// behavior manually
|
|
|
|
|
|
//
|
|
|
|
|
|
async fn run_client(
|
|
|
|
|
|
mut request_rx: tokio::sync::mpsc::Receiver<solstice_proto::ServerRequest>,
|
|
|
|
|
|
dispatcher_tx: tokio::sync::mpsc::UnboundedSender<dispatcher::Message>,
|
|
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
|
|
let address = format!(
|
|
|
|
|
|
"{}:{}",
|
|
|
|
|
|
solstice_proto::config::SERVER_HOST,
|
|
|
|
|
|
solstice_proto::config::SERVER_PORT
|
|
|
|
|
|
);
|
|
|
|
|
|
info!("Connecting to server at {}.", address);
|
|
|
|
|
|
let stream = TcpStream::connect(address).await?;
|
|
|
|
|
|
|
|
|
|
|
|
let credentials = solstice_proto::server::Credentials::new(
|
|
|
|
|
|
solstice_proto::config::USERNAME.to_string(),
|
|
|
|
|
|
solstice_proto::config::PASSWORD.to_string(),
|
|
|
|
|
|
)
|
|
|
|
|
|
.expect("Invalid credentials");
|
|
|
|
|
|
|
|
|
|
|
|
info!("Logging in to server.");
|
|
|
|
|
|
let client = solstice_proto::server::Client::new(stream)
|
|
|
|
|
|
.login(credentials)
|
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
|
|
let outbound = async_stream::stream! {
|
|
|
|
|
|
while let Some(request) = request_rx.recv().await {
|
|
|
|
|
|
yield request;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
info!("Running client.");
|
|
|
|
|
|
let inbound = client.run(outbound);
|
|
|
|
|
|
tokio::pin!(inbound);
|
|
|
|
|
|
|
|
|
|
|
|
while let Some(result) = inbound.next().await {
|
|
|
|
|
|
let response = result?;
|
|
|
|
|
|
dispatcher_tx.send(response.into())?;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
info!("Client finished running.");
|
|
|
|
|
|
Ok(())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async fn async_main() {
|
|
|
|
|
|
let (_server_request_tx, server_request_rx) = tokio::sync::mpsc::channel(100);
|
|
|
|
|
|
let (dispatcher_tx, mut dispatcher_rx) =
|
|
|
|
|
|
tokio::sync::mpsc::unbounded_channel();
|
|
|
|
|
|
|
|
|
|
|
|
let client_task = tokio::spawn(run_client(server_request_rx, dispatcher_tx));
|
|
|
|
|
|
|
|
|
|
|
|
let dispatcher = Dispatcher::new();
|
|
|
|
|
|
let executor = Executor::new(Context::new());
|
|
|
|
|
|
|
|
|
|
|
|
while let Some(message) = dispatcher_rx.recv().await {
|
|
|
|
|
|
if let Some(job) = dispatcher.dispatch(message) {
|
|
|
|
|
|
executor.schedule(job);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let _context = tokio::task::spawn_blocking(move || executor.join())
|
|
|
|
|
|
.await
|
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
|
|
client_task
|
|
|
|
|
|
.await
|
|
|
|
|
|
.expect("Client task join error")
|
|
|
|
|
|
.expect("Client error");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
|
|
|
|
|
#[tokio::main]
|
|
|
|
|
|
async fn main() {
|
|
|
env_logger::init();
|
|
|
env_logger::init();
|
|
|
|
|
|
|
|
|
let matches = App::new("solstice-client")
|
|
|
let matches = App::new("solstice-client")
|
|
|
@ -69,8 +155,10 @@ fn main() { |
|
|
.get_matches();
|
|
|
.get_matches();
|
|
|
|
|
|
|
|
|
if matches.is_present("async") {
|
|
|
if matches.is_present("async") {
|
|
|
async_main();
|
|
|
|
|
|
|
|
|
info!("Running in asynchronous mode.");
|
|
|
|
|
|
async_main().await;
|
|
|
} else {
|
|
|
} else {
|
|
|
old_main();
|
|
|
|
|
|
|
|
|
info!("Running in synchronous mode.");
|
|
|
|
|
|
tokio::task::spawn_blocking(old_main).await.unwrap();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|