jumpstorm/server/src/server.ts

175 lines
4.8 KiB
TypeScript
Raw Normal View History

2023-08-25 18:48:17 -04:00
import { Game } from '@engine/Game';
2023-08-26 19:55:27 -04:00
import { Player } from '@engine/entities';
import { Message, MessageType } from '@engine/network';
2023-08-25 20:10:09 -04:00
import { Constants } from './constants';
import {
2023-08-25 20:10:09 -04:00
ServerSocketMessageReceiver,
ServerSocketMessagePublisher,
SessionData,
ServerMessage,
2023-08-26 19:55:27 -04:00
Session,
SessionManager
2023-08-25 20:10:09 -04:00
} from './network';
import { parse } from '@engine/utils';
import { Server, ServerWebSocket } from 'bun';
2023-08-26 19:55:27 -04:00
import { Input } from '@engine/systems';
import { Control, NetworkUpdateable } from '@engine/components';
import { stringify } from '@engine/utils';
2023-08-25 20:10:09 -04:00
export class GameServer {
private server?: Server;
private game: Game;
private messageReceiver: ServerSocketMessageReceiver;
private messagePublisher: ServerSocketMessagePublisher;
2023-08-26 19:55:27 -04:00
private sessionManager: SessionManager;
2023-08-25 20:10:09 -04:00
constructor(
game: Game,
messageReceiver: ServerSocketMessageReceiver,
2023-08-26 19:55:27 -04:00
messagePublisher: ServerSocketMessagePublisher,
sessionManager: SessionManager
2023-08-25 20:10:09 -04:00
) {
this.game = game;
this.messageReceiver = messageReceiver;
this.messagePublisher = messagePublisher;
2023-08-26 19:55:27 -04:00
this.sessionManager = sessionManager;
}
2023-08-25 20:10:09 -04:00
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) => {
if (typeof msg === 'string') this.websocketMessage(ws, msg);
},
2023-08-25 20:10:09 -04:00
close: (ws) => this.closeWebsocket(ws)
}
});
2023-08-25 20:10:09 -04:00
this.messagePublisher.setServer(this.server);
2023-08-25 20:10:09 -04:00
console.log(`Listening on ${this.server.hostname}:${this.server.port}`);
}
2023-08-23 21:44:59 -04:00
2023-08-25 20:10:09 -04:00
private websocketMessage(
websocket: ServerWebSocket<SessionData>,
message: string
2023-08-25 20:10:09 -04:00
) {
const receivedMessage = parse<ServerMessage>(message);
receivedMessage.sessionData = websocket.data;
2023-08-23 21:44:59 -04:00
this.messageReceiver.addMessage(receivedMessage);
}
2023-08-25 20:10:09 -04:00
private closeWebsocket(websocket: ServerWebSocket<SessionData>) {
const { sessionId } = websocket.data;
2023-08-23 21:44:59 -04:00
2023-08-26 19:55:27 -04:00
const sessionEntities =
this.sessionManager.getSession(sessionId)?.controllableEntities;
2023-08-26 19:55:27 -04:00
this.sessionManager.removeSession(sessionId);
2023-08-23 21:44:59 -04:00
2023-08-25 20:10:09 -04:00
if (!sessionEntities) return;
2023-08-26 19:55:27 -04:00
sessionEntities.forEach((id) => this.game.removeEntity(id));
2023-08-26 15:54:39 -04:00
2023-08-25 20:10:09 -04:00
this.messagePublisher.addMessage({
type: MessageType.REMOVE_ENTITIES,
body: Array.from(sessionEntities)
});
2023-08-23 21:44:59 -04:00
}
2023-08-25 20:10:09 -04:00
private openWebsocket(websocket: ServerWebSocket<SessionData>) {
websocket.subscribe(Constants.GAME_TOPIC);
2023-08-25 20:10:09 -04:00
const { sessionId } = websocket.data;
2023-08-26 19:55:27 -04:00
if (this.sessionManager.getSession(sessionId)) {
2023-08-25 20:10:09 -04:00
return;
}
2023-08-26 19:55:27 -04:00
const newSession: Session = {
2023-08-25 20:10:09 -04:00
sessionId,
2023-08-26 19:55:27 -04:00
controllableEntities: new Set(),
inputSystem: new Input(sessionId)
};
2023-08-25 20:10:09 -04:00
2023-08-26 19:55:27 -04:00
const player = new Player();
player.addComponent(new Control(sessionId));
player.addComponent(new NetworkUpdateable());
2023-08-25 20:10:09 -04:00
this.game.addEntity(player);
2023-08-26 19:55:27 -04:00
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 = {
2023-08-25 20:10:09 -04:00
type: MessageType.NEW_ENTITIES,
body: [
{
2023-08-26 19:55:27 -04:00
id: player.id,
entityName: player.name,
args: player.serialize()
2023-08-25 20:10:09 -04:00
}
]
2023-08-26 19:55:27 -04:00
};
this.messagePublisher.addMessage(addNewPlayer);
2023-08-25 20:10:09 -04:00
}
2023-08-25 18:48:17 -04:00
2023-08-26 15:54:39 -04:00
private fetchHandler(req: Request, server: Server): Response {
2023-08-23 21:44:59 -04:00
const url = new URL(req.url);
const headers = new Headers();
2023-08-25 18:48:17 -04:00
headers.set('Access-Control-Allow-Origin', '*');
if (url.pathname == '/assign') {
2023-08-26 19:55:27 -04:00
if (this.sessionManager.numSessions() > Constants.MAX_PLAYERS)
2023-08-25 18:48:17 -04:00
return new Response('too many players', { headers, status: 400 });
2023-08-23 21:44:59 -04:00
const sessionId = crypto.randomUUID();
2023-08-25 18:48:17 -04:00
headers.set('Set-Cookie', `SessionId=${sessionId};`);
2023-08-23 21:44:59 -04:00
return new Response(sessionId, { headers });
}
2023-08-25 18:48:17 -04:00
const cookie = req.headers.get('cookie');
2023-08-23 21:44:59 -04:00
if (!cookie) {
2023-08-25 18:48:17 -04:00
return new Response('No session', { headers, status: 401 });
2023-08-23 21:44:59 -04:00
}
2023-08-25 18:48:17 -04:00
const sessionId = cookie.split(';').at(0)!.split('SessionId=').at(1);
2023-08-23 21:44:59 -04:00
2023-08-25 18:48:17 -04:00
if (url.pathname == '/game') {
2023-08-23 21:44:59 -04:00
server.upgrade(req, {
headers,
data: {
2023-08-25 18:48:17 -04:00
sessionId
}
2023-08-23 21:44:59 -04:00
});
2023-08-25 20:10:09 -04:00
return new Response('upgraded to ws', { headers });
2023-08-23 21:44:59 -04:00
}
2023-08-25 18:48:17 -04:00
if (url.pathname == '/me') {
2023-08-23 21:44:59 -04:00
return new Response(sessionId, { headers });
}
2023-08-25 18:48:17 -04:00
return new Response('Not found', { headers, status: 404 });
}
2023-08-25 20:10:09 -04:00
}