diff options
Diffstat (limited to 'server/src/server.ts')
-rw-r--r-- | server/src/server.ts | 197 |
1 files changed, 167 insertions, 30 deletions
diff --git a/server/src/server.ts b/server/src/server.ts index 74d901b..575e916 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -1,37 +1,174 @@ -import { Game } from "../../engine/Game"; -import { Floor, Player } from "../../engine/entities"; -import { WallBounds, Physics, Collision } from "../../engine/systems"; -import { Miscellaneous } from "../../engine/config"; +import { Game } from '@engine/Game'; +import { Player } from '@engine/entities'; +import { Message, MessageType } from '@engine/network'; +import { Constants } from './constants'; +import { + ServerSocketMessageReceiver, + ServerSocketMessagePublisher, + SessionData, + ServerMessage, + Session, + SessionManager +} from './network'; +import { parse } from '@engine/utils'; +import { Server, ServerWebSocket } from 'bun'; +import { Input } from '@engine/systems'; +import { Control, NetworkUpdateable } from '@engine/components'; +import { stringify } from '@engine/utils'; -const TICK_RATE = 60 / 1000; +export class GameServer { + private server?: Server; + private game: Game; + private messageReceiver: ServerSocketMessageReceiver; + private messagePublisher: ServerSocketMessagePublisher; + private sessionManager: SessionManager; -const game = new Game(); + constructor( + game: Game, + messageReceiver: ServerSocketMessageReceiver, + messagePublisher: ServerSocketMessagePublisher, + sessionManager: SessionManager + ) { + this.game = game; + this.messageReceiver = messageReceiver; + this.messagePublisher = messagePublisher; + this.sessionManager = sessionManager; + } -[ - new Physics(), - new Collision({ width: Miscellaneous.WIDTH, height: Miscellaneous.HEIGHT }), - new WallBounds(Miscellaneous.WIDTH), -].forEach((system) => game.addSystem(system)); + public serve() { + if (!this.server) + this.server = Bun.serve<SessionData>({ + port: Constants.SERVER_PORT, + fetch: (req, srv) => this.fetchHandler(req, srv), + websocket: { + open: (ws) => this.openWebsocket(ws), + message: (ws, msg) => this.websocketMessage(ws, msg), + close: (ws) => this.closeWebsocket(ws) + } + }); -[new Floor(160), new Player()].forEach((entity) => game.addEntity(entity)); + this.messagePublisher.setServer(this.server); -game.start(); -setInterval(() => { - game.doGameLoop(performance.now()); -}, TICK_RATE); + console.log(`Listening on ${this.server.hostname}:${this.server.port}`); + } -const server = Bun.serve<>({ - port: 8080, - fetch(req, server) { - server.upgrade(req, { - data: {}, + private websocketMessage( + websocket: ServerWebSocket<SessionData>, + message: string | Uint8Array + ) { + if (typeof message == 'string') { + const receivedMessage = parse<ServerMessage>(message); + receivedMessage.sessionData = websocket.data; + + this.messageReceiver.addMessage(receivedMessage); + } + } + + private closeWebsocket(websocket: ServerWebSocket<SessionData>) { + const { sessionId } = websocket.data; + + const sessionEntities = + this.sessionManager.getSession(sessionId)!.controllableEntities; + this.sessionManager.removeSession(sessionId); + + if (!sessionEntities) return; + sessionEntities.forEach((id) => this.game.removeEntity(id)); + + this.messagePublisher.addMessage({ + type: MessageType.REMOVE_ENTITIES, + body: Array.from(sessionEntities) }); - }, - websocket: { - // handler called when a message is received - async message(ws, message) { - console.log(`Received ${message}`); - }, - }, -}); -console.log(`Listening on localhost:${server.port}`); + } + + private openWebsocket(websocket: ServerWebSocket<SessionData>) { + websocket.subscribe(Constants.GAME_TOPIC); + + const { sessionId } = websocket.data; + if (this.sessionManager.getSession(sessionId)) { + return; + } + + const newSession: Session = { + sessionId, + controllableEntities: new Set(), + inputSystem: new Input(sessionId) + }; + + const player = new Player(); + player.addComponent(new Control(sessionId)); + player.addComponent(new NetworkUpdateable()); + this.game.addEntity(player); + + newSession.controllableEntities.add(player.id); + this.sessionManager.putSession(sessionId, newSession); + + const addCurrentEntities: Message[] = [ + { + type: MessageType.NEW_ENTITIES, + body: Array.from(this.game.entities.values()) + .filter((entity) => entity.id != player.id) + .map((entity) => { + return { + id: entity.id, + entityName: entity.name, + args: entity.serialize() + }; + }) + } + ]; + websocket.sendText(stringify(addCurrentEntities)); + + const addNewPlayer: Message = { + type: MessageType.NEW_ENTITIES, + body: [ + { + id: player.id, + entityName: player.name, + args: player.serialize() + } + ] + }; + this.messagePublisher.addMessage(addNewPlayer); + } + + private fetchHandler(req: Request, server: Server): Response { + const url = new URL(req.url); + + const headers = new Headers(); + headers.set('Access-Control-Allow-Origin', '*'); + + if (url.pathname == '/assign') { + if (this.sessionManager.numSessions() > Constants.MAX_PLAYERS) + return new Response('too many players', { headers, status: 400 }); + + const sessionId = crypto.randomUUID(); + headers.set('Set-Cookie', `SessionId=${sessionId};`); + + return new Response(sessionId, { headers }); + } + + const cookie = req.headers.get('cookie'); + if (!cookie) { + return new Response('No session', { headers, status: 401 }); + } + + const sessionId = cookie.split(';').at(0)!.split('SessionId=').at(1); + + if (url.pathname == '/game') { + server.upgrade(req, { + headers, + data: { + sessionId + } + }); + + return new Response('upgraded to ws', { headers }); + } + + if (url.pathname == '/me') { + return new Response(sessionId, { headers }); + } + + return new Response('Not found', { headers, status: 404 }); + } +} |