summaryrefslogtreecommitdiff
path: root/server/src/server.ts
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/server.ts')
-rw-r--r--server/src/server.ts197
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 });
+ }
+}