Created
May 10, 2024 19:29
-
-
Save michidk/8647ba23facbb33bd8626376a0ae2b4d to your computer and use it in GitHub Desktop.
Fix meh plz
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::borrow::BorrowMut; | |
use std::collections::HashMap; | |
use std::hash::Hash; | |
use std::sync::Arc; | |
use async_trait::async_trait; | |
use log::debug; | |
use ratatui::backend::CrosstermBackend; | |
use ratatui::layout::Rect; | |
use ratatui::style::Color; | |
use ratatui::widgets::canvas::*; | |
use ratatui::widgets::{Block, Borders, Widget}; | |
use ratatui::Terminal; | |
use russh::server::*; | |
use russh::{Channel, ChannelId}; | |
use tokio::sync::Mutex; | |
type SshTerminal = Terminal<CrosstermBackend<TerminalHandle>>; | |
#[derive(Clone)] | |
struct TerminalHandle { | |
handle: Handle, | |
// The sink collects the data which is finally flushed to the handle. | |
sink: Vec<u8>, | |
channel_id: ChannelId, | |
} | |
// The crossterm backend writes to the terminal handle. | |
impl std::io::Write for TerminalHandle { | |
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { | |
self.sink.extend_from_slice(buf); | |
Ok(buf.len()) | |
} | |
fn flush(&mut self) -> std::io::Result<()> { | |
let handle = self.handle.clone(); | |
let channel_id = self.channel_id; | |
let data = self.sink.clone().into(); | |
futures::executor::block_on(async move { | |
let result = handle.data(channel_id, data).await; | |
if result.is_err() { | |
eprintln!("Failed to send data: {:?}", result); | |
} | |
}); | |
self.sink.clear(); | |
Ok(()) | |
} | |
} | |
#[derive(Debug, Clone, Copy, PartialEq, Eq)] | |
struct Position2d { | |
x: u16, | |
y: u16, | |
} | |
#[derive(Debug, Clone, Copy, PartialEq)] | |
struct Padel { | |
position: f64, | |
} | |
#[derive(Debug, Clone)] | |
struct GameState { | |
left_padel: Padel, | |
right_padel: Padel, | |
ball_position: Position2d, | |
} | |
impl GameState { | |
fn new() -> Self { | |
Self { | |
left_padel: Padel { position: 0f64 }, | |
right_padel: Padel { position: 0f64 }, | |
ball_position: Position2d { x: 5, y: 5 }, | |
} | |
} | |
} | |
#[derive(Clone)] | |
struct Lobby { | |
name: String, | |
left_player: Option<Arc<Mutex<Player>>>, | |
right_player: Option<Arc<Mutex<Player>>>, | |
spectators: Vec<Arc<Mutex<Player>>>, | |
state: GameState, | |
} | |
#[derive(Clone)] | |
struct Player { | |
terminal: SshTerminal, | |
lobby: Arc<Mutex<Lobby>>, | |
} | |
impl Player { | |
fn new(terminal: SshTerminal, lobby: Arc<Mutex<Lobby>>) -> Self { | |
Self { terminal, lobby } | |
} | |
} | |
#[derive(Clone)] | |
struct GameServer { | |
players: Arc<Mutex<HashMap<usize, Arc<Mutex<Player>>>>>, | |
lobby: Arc<Mutex<Lobby>>, | |
id: usize, | |
} | |
impl GameServer { | |
pub fn new() -> Self { | |
Self { | |
players: Arc::new(Mutex::new(HashMap::new())), | |
lobby: Arc::new(Mutex::new(Lobby { | |
name: "lobby".to_string(), | |
left_player: None, | |
right_player: None, | |
spectators: Vec::new(), | |
state: GameState::new(), | |
})), | |
id: 0, | |
} | |
} | |
fn draw(state: GameState, area: Rect) -> impl Widget { | |
Canvas::default() | |
.block(Block::default().title("Game").borders(Borders::ALL)) | |
.paint(move |ctx| { | |
ctx.draw(&Rectangle { | |
x: 0.0, | |
y: state.left_padel.position + 5.0, | |
width: 2.0, | |
height: 10.0, | |
color: Color::Red, | |
}); | |
ctx.draw(&Rectangle { | |
x: area.width as f64 - 2.0, | |
y: state.right_padel.position + 5.0, | |
width: 2.0, | |
height: 10.0, | |
color: Color::Green, | |
}); | |
ctx.draw(&Rectangle { | |
x: state.ball_position.x as f64, | |
y: state.ball_position.y as f64, | |
width: 2.0, | |
height: 2.0, | |
color: Color::Blue, | |
}); | |
}) | |
.x_bounds([0.0, area.width as f64]) | |
.y_bounds([0.0, area.height as f64]) | |
} | |
async fn render(lobby: &Lobby, player: &mut Player) { | |
player | |
.terminal | |
.draw(|f| { | |
let area = f.size(); | |
f.render_widget(Self::draw(lobby.state.clone(), area), area); | |
}) | |
.expect("Failed to draw"); | |
} | |
pub async fn run(&mut self) -> Result<(), anyhow::Error> { | |
let players = self.players.clone(); | |
let lobby = self.lobby.clone(); | |
tokio::spawn(async move { | |
loop { | |
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; | |
for (_, player) in players.lock().await.iter_mut() { | |
Self::render(&*lobby.lock().await, &mut *player.lock().await).await; | |
} | |
} | |
}); | |
let config = Config { | |
inactivity_timeout: Some(std::time::Duration::from_secs(3600)), | |
auth_rejection_time: std::time::Duration::from_secs(3), | |
auth_rejection_time_initial: Some(std::time::Duration::from_secs(0)), | |
keys: vec![russh_keys::key::KeyPair::generate_ed25519().unwrap()], | |
..Default::default() | |
}; | |
self.run_on_address(Arc::new(config), ("0.0.0.0", 2222)) | |
.await?; | |
Ok(()) | |
} | |
} | |
impl Server for GameServer { | |
type Handler = Self; | |
fn new_client(&mut self, _: Option<std::net::SocketAddr>) -> Self { | |
let s = self.clone(); | |
self.id += 1; | |
s | |
} | |
} | |
#[async_trait] | |
impl Handler for GameServer { | |
type Error = anyhow::Error; | |
async fn channel_open_session( | |
&mut self, | |
channel: Channel<Msg>, | |
session: &mut Session, | |
) -> Result<bool, Self::Error> { | |
{ | |
{ | |
let mut players = self.players.lock().await; | |
let terminal_handle = TerminalHandle { | |
handle: session.handle(), | |
sink: Vec::new(), | |
channel_id: channel.id(), | |
}; | |
let backend = CrosstermBackend::new(terminal_handle.clone()); | |
let mut terminal = Terminal::new(backend)?; | |
terminal.clear()?; | |
let player = Arc::new(Mutex::new(Player::new(terminal, self.lobby.clone()))); | |
players.insert(self.id, player.clone()); | |
// assign player to lobby | |
let mut lobby = self.lobby.lock().await; | |
if lobby.left_player.is_none() { | |
lobby.left_player = Some(player); | |
debug!("left player assigned"); | |
} else if lobby.right_player.is_none() { | |
lobby.right_player = Some(player); | |
debug!("right player assigned"); | |
} else { | |
lobby.spectators.push(player); | |
debug!("spectator assigned"); | |
} | |
} | |
// self.send_update().await; | |
} | |
Ok(true) | |
} | |
async fn auth_none(&mut self, _: &str) -> Result<Auth, Self::Error> { | |
Ok(russh::server::Auth::Accept) | |
} | |
async fn data( | |
&mut self, | |
channel: ChannelId, | |
data: &[u8], | |
session: &mut Session, | |
) -> Result<(), Self::Error> { | |
debug!("Got data: {:?}", data); | |
{ | |
let mut players = self.players.lock().await; | |
let player_ref = players.get_mut(&self.id).expect("player not found"); | |
let player = player_ref.lock().await; | |
let mut lobby = player.lobby.lock().await; | |
let mut padel: Option<&mut Padel> = None; | |
let update_left_padel = lobby | |
.left_player | |
.as_ref() | |
.map_or(false, |left_player| Arc::ptr_eq(left_player, &player_ref)); | |
let update_right_padel = lobby | |
.right_player | |
.as_ref() | |
.map_or(false, |right_player| Arc::ptr_eq(right_player, &player_ref)); | |
// Now, perform the mutable operation outside the checks | |
if update_left_padel { | |
padel = Some(&mut lobby.state.left_padel); | |
debug!("left padel updated"); | |
} | |
if update_right_padel { | |
padel = Some(&mut lobby.state.right_padel); | |
debug!("right padel updated"); | |
} | |
if let Some(padel) = padel { | |
// match arrow keys and wasd | |
if data.len() >= 3 { | |
// ANSI escape code | |
if data[0] == 0x1b && data[1] == 0x5b { | |
match data[2] { | |
0x41 => padel.position += 1.0, | |
0x42 => padel.position -= 1.0, | |
_ => {} | |
} | |
} | |
} else { | |
// Raw input | |
match data[0] { | |
b'w' => padel.position += 1.0, | |
b's' => padel.position -= 1.0, | |
b'q' => { | |
session.close(channel); | |
} | |
_ => {} | |
} | |
} | |
debug!("padel updated, new position: {:?}", padel.position); | |
} | |
} | |
// self.send_update().await; | |
Ok(()) | |
} | |
/// The client's window size has changed. | |
async fn window_change_request( | |
&mut self, | |
_: ChannelId, | |
col_width: u32, | |
row_height: u32, | |
_: u32, | |
_: u32, | |
_: &mut Session, | |
) -> Result<(), Self::Error> { | |
{ | |
let mut players = self.players.lock().await; | |
let player = players.get_mut(&self.id).unwrap(); | |
let rect = Rect { | |
x: 0, | |
y: 0, | |
width: col_width as u16, | |
height: row_height as u16, | |
}; | |
player.lock().await.terminal.resize(rect)?; | |
} | |
Ok(()) | |
} | |
} | |
#[tokio::main] | |
async fn main() { | |
env_logger::builder() | |
.filter_level(log::LevelFilter::Debug) | |
.filter_module("russh::cipher", log::LevelFilter::Info) | |
.filter_module("russh::server", log::LevelFilter::Info) | |
.filter_module("russh::ssh_read", log::LevelFilter::Info) | |
.filter_module("russh::negotiation", log::LevelFilter::Info) | |
.init(); | |
let mut server = GameServer::new(); | |
server.run().await.expect("Failed running server"); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment