Skip to content

Commit

Permalink
Improve websocekts
Browse files Browse the repository at this point in the history
  • Loading branch information
Comeza committed May 25, 2024
1 parent 845e748 commit c72181c
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 71 deletions.
2 changes: 1 addition & 1 deletion backend/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ fn main() -> std::io::Result<()> {
let ws_listener = TcpListener::bind("0.0.0.0:1213")?;
listener.set_nonblocking(true)?;
ws_listener.set_nonblocking(true)?;
let mut game = GameState::new(100);
let mut game = GameState::new(50);
loop {
if let Err(e) = network::accept_new_connections(&listener, &mut game) {
eprintln!("Error while accepting a new connection: {e}");
Expand Down
Binary file modified frontend/bun.lockb
Binary file not shown.
61 changes: 30 additions & 31 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,50 @@
import { Board } from "Board"
import { GameStateContext, GameStateExt, GameStateProvider, parseMsg } from "lib/game";
import { WebSocketProvider, } from "lib/ws";
import { GameStateContext, GameStateProvider, parseMsg } from "lib/game";
import { WebSocketContext, WebSocketProvider, } from "lib/ws";
import { QRCodeSVG } from "qrcode.react"
import { useContext, useEffect, useState } from "react";

export function App() {
return <GameStateProvider>
<WebSocketProvider url="ws://localhost:1213">

function updateState(msg: string, gameStateExt: GameStateExt | null) {
return gameStateExt?.setState((state) => {
return ({
board: parseMsg(msg),
turn: state.turn + 1
})
});
}

return (<GameStateProvider>

<GameStateContext.Consumer>
{gameState =>
<WebSocketProvider url="ws://localhost:1213" onMsg={(msg) => updateState(msg, gameState)}>

<div className="flex justify-center items-center w-dvw h-dvh">
<div className="flex flex-row h-dvh py-10">
<BoardWrapper />
<Sidebar />
</div >
</div>
<div className="flex justify-center items-center w-dvw h-dvh">
<div className="flex flex-row h-dvh py-10">
<Game />
<Sidebar />
</div >
</div>

</WebSocketProvider>
}
</GameStateContext.Consumer>
</GameStateProvider>)
</WebSocketProvider>
</GameStateProvider>
}

function ConnectionUnavailable() {
return <div className="flex flex-col jusify-center items-center">
<h1 className="text-xl text-gray-500">Waiting for the Server to start</h1>
</div>
}

function BoardWrapper() {
function Game() {
const gameState = useContext(GameStateContext);
const websocket = useContext(WebSocketContext);

websocket?.registerHandler("BOARD", (msg) => gameState?.setState((state) => ({
board: parseMsg(msg),
turn: state.turn + 1
})));

const width = gameState?.board.width || 3;
const height = gameState?.board.height || 3;

if (!websocket?.isOpen()) return <ConnectionUnavailable />

return <Board width={width} height={height} board={gameState?.board.board || undefined} />
}


function Sidebar() {
const ws = useContext(WebSocketContext);
const gameState = useContext(GameStateContext);
const [time, setTime] = useState("00:00");

Expand All @@ -65,11 +64,11 @@ function Sidebar() {
<span>TURN: {gameState?.turn || 0}</span>
<span>TIME: {time}</span>
<span>SIZE: {gameState?.board.width || 3}x{gameState?.board.height || 3}</span>
<span>SERVER: {ws?.isOpen() ? "CONNECTED" : "LOST"}</span>
<hr className="bg-black h-[3px] py-2 mb-4" />
<a href="https://github.com/Lila-Kuhlt/mmgo" rel="noreferrer" target="_blank" >
<QRCodeSVG value="https://github.com/Lila-Kuhlt/mmgo" />
<a href="https://github.com/Lila-Kuhlt/mmgo" rel="noreferrer" target="_blank" className="w-fill flex justify-center">
<QRCodeSVG value="https://github.com/Lila-Kuhlt/mmgo" size={190}/>
</a>
<hr className="bg-black h-[3px] py-2 my-4" />
</div>
</div>
}
4 changes: 2 additions & 2 deletions frontend/src/lib/game.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ export function GameStateProvider(props: PropsWithChildren) {
}


export function parseMsg(msg: string): Board {
const [_, startStr, widthStr, heightStr, pieces] = msg.split(" ");
export function parseMsg(msg: string[]): Board {
const [startStr, widthStr, heightStr, pieces] = msg;

const start = new Date(parseInt(startStr));
const width = parseInt(widthStr);
Expand Down
105 changes: 68 additions & 37 deletions frontend/src/lib/ws.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,84 @@
import { createContext, useState, useEffect, useRef, PropsWithChildren } from 'react';
import { createContext, useState, useEffect, PropsWithChildren } from 'react';

export interface WebSocketState {
ws: WebSocket | null;
isConnected: boolean;
}
/**
* WebSocket Disconnect Codes
* @see https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code
*/
const WEBSOCKET_CODES = {
CLOSE_NORMAL: 1000,
CLOSE_GOING_AWAY: 1001,
CLOSE_ABNORMAL: 1006,
SERVER_ERROR: 1011,
SERVICE_RESTART: 1012,
};

export interface WebsocketProviderProps {
url: string,
onMsg: (msg: string) => void
}
export type MsgHandler = (msg: string[]) => void;

export const WebSocketContext = createContext<WebSocketState | null>(null);
export class WebSocketHandler {

export function WebSocketProvider(props: PropsWithChildren<WebsocketProviderProps>) {
const [ws, setWs] = useState<WebSocket | null>(null);
const [isConnected, setIsConnected] = useState<boolean>(false);
const reconnectInterval = useRef<number | null>(null);
private handlers: Record<string, MsgHandler> = {}
private ws?: WebSocket;
private addr: string;
private reconnectHandle?: number;
private reconnectInterval: number = 1200;

const connect = () => {
const newWs = new WebSocket(props.url);
setWs(newWs);
};
constructor(ws: string) {
this.addr = ws;
this.connect();
}

useEffect(() => {
if (!ws) return;
public isOpen() {
return this.ws?.readyState === WebSocket.OPEN
}

ws.onopen = () => setIsConnected(true);
ws.onmessage = (event: MessageEvent) => props.onMsg?.(event.data);
private connect() {
this.ws = new WebSocket(this.addr);
this.ws.onopen = this.onOpen.bind(this);
this.ws.onclose = this.onClose.bind(this);
this.ws.onmessage = this.onMessage.bind(this);
}

ws.onclose = () => {
setIsConnected(false);
reconnectInterval.current && clearTimeout(reconnectInterval.current);
reconnectInterval.current = setTimeout(connect, 5000);
};

return () => {
ws.close();
reconnectInterval.current && clearTimeout(reconnectInterval.current);
};
public disconnect() {
this.ws?.close();
}

}, [ws]);
public registerHandler(what: string, handler: MsgHandler) {
this.handlers[what] = handler;
}

useEffect(() => {
if (!isConnected) {
connect();
private onMessage(message: MessageEvent) {
if (typeof message.data !== "string") return;
const msg = message.data.split(' ');
this.handlers[msg[0].toUpperCase()]?.(msg.slice(1));
}

private onOpen() {
clearInterval(this.reconnectHandle)
}

private onClose() {
if (this.reconnectHandle === undefined) {
this.connect()
return;
}
}, [isConnected]);

return <WebSocketContext.Provider value={{ ws, isConnected }}>
this.reconnectHandle = setInterval(() => this.connect(), this.reconnectInterval);
}
}

export const WebSocketContext = createContext<WebSocketHandler | null>(null);

export function WebSocketProvider(props: PropsWithChildren<{ url: string }>) {
const [ws, setWs] = useState<WebSocketHandler | null>(null);

useEffect(() => {
const ws = new WebSocketHandler(props.url);
setWs(ws);
return () => ws.disconnect();
}, [props.url]);

return <WebSocketContext.Provider value={ws}>
{props.children}
</WebSocketContext.Provider>
};

0 comments on commit c72181c

Please sign in to comment.