refactor server structure
This commit is contained in:
parent
773ce84f4b
commit
2fbe0f0595
6
server/src/constants.ts
Normal file
6
server/src/constants.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export namespace Constants {
|
||||
export const SERVER_PORT = 8080;
|
||||
export const SERVER_TICK_RATE = (1 / 60) * 1000;
|
||||
export const GAME_TOPIC = 'game';
|
||||
export const MAX_PLAYERS = 8;
|
||||
}
|
32
server/src/main.ts
Normal file
32
server/src/main.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { Grid } from '@engine/structures';
|
||||
import {
|
||||
ServerMessageProcessor,
|
||||
ServerSocketMessagePublisher,
|
||||
ServerSocketMessageReceiver
|
||||
} from './network';
|
||||
import { Collision, NetworkUpdate, Physics, WallBounds } from '@engine/systems';
|
||||
import { Game } from '@engine/Game';
|
||||
import { Constants } from './constants';
|
||||
import { GameServer } from './server';
|
||||
|
||||
const messageReceiver = new ServerSocketMessageReceiver();
|
||||
const messagePublisher = new ServerSocketMessagePublisher();
|
||||
const messageProcessor = new ServerMessageProcessor();
|
||||
|
||||
const game = new Game();
|
||||
|
||||
const server = new GameServer(game, messageReceiver, messagePublisher);
|
||||
|
||||
[
|
||||
new Physics(),
|
||||
new Collision(new Grid()),
|
||||
new WallBounds(),
|
||||
new NetworkUpdate(messageReceiver, messagePublisher, messageProcessor)
|
||||
].forEach((system) => game.addSystem(system));
|
||||
|
||||
game.start();
|
||||
setInterval(() => {
|
||||
game.doGameLoop(performance.now());
|
||||
}, Constants.SERVER_TICK_RATE);
|
||||
|
||||
server.serve();
|
8
server/src/network/MessageProcessor.ts
Normal file
8
server/src/network/MessageProcessor.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { MessageProcessor } from '@engine/network';
|
||||
import { ServerMessage } from '.';
|
||||
|
||||
export class ServerMessageProcessor implements MessageProcessor {
|
||||
constructor() {}
|
||||
|
||||
public process(_message: ServerMessage) {}
|
||||
}
|
31
server/src/network/MessagePublisher.ts
Normal file
31
server/src/network/MessagePublisher.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { Message, MessagePublisher } from '@engine/network';
|
||||
import { Server } from 'bun';
|
||||
import { Constants } from '../constants';
|
||||
import { stringify } from '@engine/utils';
|
||||
|
||||
export class ServerSocketMessagePublisher implements MessagePublisher {
|
||||
private server?: Server;
|
||||
private messages: Message[];
|
||||
|
||||
constructor(server?: Server) {
|
||||
this.messages = [];
|
||||
|
||||
if (server) this.setServer(server);
|
||||
}
|
||||
|
||||
public setServer(server: Server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public addMessage(message: Message) {
|
||||
this.messages.push(message);
|
||||
}
|
||||
|
||||
public publish() {
|
||||
if (this.messages.length) {
|
||||
this.server?.publish(Constants.GAME_TOPIC, stringify(this.messages));
|
||||
|
||||
this.messages = [];
|
||||
}
|
||||
}
|
||||
}
|
22
server/src/network/MessageReceiver.ts
Normal file
22
server/src/network/MessageReceiver.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { MessageQueueProvider } from '@engine/network';
|
||||
import type { ServerMessage } from '.';
|
||||
|
||||
export class ServerSocketMessageReceiver implements MessageQueueProvider {
|
||||
private messages: ServerMessage[];
|
||||
|
||||
constructor() {
|
||||
this.messages = [];
|
||||
}
|
||||
|
||||
public addMessage(message: ServerMessage) {
|
||||
this.messages.push(message);
|
||||
}
|
||||
|
||||
public getNewMessages() {
|
||||
return this.messages;
|
||||
}
|
||||
|
||||
public clearMessages() {
|
||||
this.messages = [];
|
||||
}
|
||||
}
|
16
server/src/network/index.ts
Normal file
16
server/src/network/index.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Message } from '@engine/network';
|
||||
|
||||
export * from './MessageProcessor';
|
||||
export * from './MessagePublisher';
|
||||
export * from './MessageReceiver';
|
||||
|
||||
export type SessionData = { sessionId: string };
|
||||
|
||||
export type Session = {
|
||||
sessionId: string;
|
||||
controllableEntities: Set<string>;
|
||||
};
|
||||
|
||||
export interface ServerMessage extends Message {
|
||||
sessionData: SessionData;
|
||||
}
|
@ -1,110 +1,124 @@
|
||||
import { Game } from '@engine/Game';
|
||||
import { EntityNames, Player } from '@engine/entities';
|
||||
import { WallBounds, Physics, Collision, NetworkUpdate } from '@engine/systems';
|
||||
import { MessageType } from '@engine/network';
|
||||
import { Constants } from './constants';
|
||||
import {
|
||||
type MessageQueueProvider,
|
||||
type MessagePublisher,
|
||||
MessageType,
|
||||
type MessageProcessor,
|
||||
type Message
|
||||
} from '@engine/network';
|
||||
import { stringify, parse } from '@engine/utils';
|
||||
import { Grid } from '@engine/structures';
|
||||
import { Miscellaneous } from '@engine/config';
|
||||
import { Server } from 'bun';
|
||||
ServerSocketMessageReceiver,
|
||||
ServerSocketMessagePublisher,
|
||||
SessionData,
|
||||
ServerMessage,
|
||||
Session
|
||||
} from './network';
|
||||
import { parse } from '@engine/utils';
|
||||
import { Server, ServerWebSocket } from 'bun';
|
||||
|
||||
const SERVER_PORT = 8080;
|
||||
const SERVER_TICK_RATE = (1 / 60) * 1000;
|
||||
const GAME_TOPIC = 'game';
|
||||
const MAX_PLAYERS = 8;
|
||||
export class GameServer {
|
||||
private sessions: Map<string, Session>;
|
||||
|
||||
type SessionData = { sessionId: string };
|
||||
|
||||
interface ServerMessage extends Message {
|
||||
sessionData: SessionData;
|
||||
}
|
||||
|
||||
class ServerSocketMessageReceiver implements MessageQueueProvider {
|
||||
private messages: ServerMessage[];
|
||||
|
||||
constructor() {
|
||||
this.messages = [];
|
||||
}
|
||||
|
||||
public addMessage(message: ServerMessage) {
|
||||
this.messages.push(message);
|
||||
}
|
||||
|
||||
public getNewMessages() {
|
||||
return this.messages;
|
||||
}
|
||||
|
||||
public clearMessages() {
|
||||
this.messages = [];
|
||||
}
|
||||
}
|
||||
|
||||
class ServerMessageProcessor implements MessageProcessor {
|
||||
constructor() {}
|
||||
|
||||
public process(_message: ServerMessage) {}
|
||||
}
|
||||
|
||||
class ServerSocketMessagePublisher implements MessagePublisher {
|
||||
private server?: Server;
|
||||
private messages: Message[];
|
||||
private game: Game;
|
||||
private messageReceiver: ServerSocketMessageReceiver;
|
||||
private messagePublisher: ServerSocketMessagePublisher;
|
||||
|
||||
constructor(server?: Server) {
|
||||
if (server) {
|
||||
this.server = server;
|
||||
}
|
||||
constructor(
|
||||
game: Game,
|
||||
messageReceiver: ServerSocketMessageReceiver,
|
||||
messagePublisher: ServerSocketMessagePublisher
|
||||
) {
|
||||
this.sessions = new Map();
|
||||
|
||||
this.messages = [];
|
||||
this.game = game;
|
||||
this.messageReceiver = messageReceiver;
|
||||
this.messagePublisher = messagePublisher;
|
||||
}
|
||||
|
||||
public setServer(server: Server) {
|
||||
this.server = server;
|
||||
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)
|
||||
}
|
||||
});
|
||||
|
||||
this.messagePublisher.setServer(this.server);
|
||||
|
||||
console.log(`Listening on ${this.server.hostname}:${this.server.port}`);
|
||||
}
|
||||
|
||||
public addMessage(message: Message) {
|
||||
this.messages.push(message);
|
||||
}
|
||||
private websocketMessage(
|
||||
websocket: ServerWebSocket<SessionData>,
|
||||
message: string | Uint8Array
|
||||
) {
|
||||
if (typeof message == 'string') {
|
||||
const receivedMessage = parse<ServerMessage>(message);
|
||||
receivedMessage.sessionData = websocket.data;
|
||||
|
||||
public publish() {
|
||||
if (this.messages.length) {
|
||||
this.server?.publish(GAME_TOPIC, stringify(this.messages));
|
||||
|
||||
this.messages = [];
|
||||
this.messageReceiver.addMessage(receivedMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const game = new Game();
|
||||
private closeWebsocket(websocket: ServerWebSocket<SessionData>) {
|
||||
const { sessionId } = websocket.data;
|
||||
|
||||
const messageReceiver = new ServerSocketMessageReceiver();
|
||||
const messagePublisher = new ServerSocketMessagePublisher();
|
||||
const messageProcessor = new ServerMessageProcessor();
|
||||
const sessionControllableEntities: Map<string, Set<string>> = new Map();
|
||||
const sessionEntities = this.sessions.get(sessionId)!.controllableEntities;
|
||||
|
||||
const sessions = new Set<string>();
|
||||
this.sessions.delete(sessionId);
|
||||
|
||||
const server = Bun.serve<SessionData>({
|
||||
port: SERVER_PORT,
|
||||
fetch: async (req, server): Promise<Response> => {
|
||||
if (!sessionEntities) return;
|
||||
this.messagePublisher.addMessage({
|
||||
type: MessageType.REMOVE_ENTITIES,
|
||||
body: Array.from(sessionEntities)
|
||||
});
|
||||
}
|
||||
|
||||
private openWebsocket(websocket: ServerWebSocket<SessionData>) {
|
||||
websocket.subscribe(Constants.GAME_TOPIC);
|
||||
|
||||
const { sessionId } = websocket.data;
|
||||
if (this.sessions.has(sessionId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.sessions.set(sessionId, {
|
||||
sessionId,
|
||||
controllableEntities: new Set()
|
||||
});
|
||||
|
||||
const player = new Player(sessionId);
|
||||
this.game.addEntity(player);
|
||||
this.messagePublisher.addMessage({
|
||||
type: MessageType.NEW_ENTITIES,
|
||||
body: [
|
||||
{
|
||||
entityName: EntityNames.Player,
|
||||
args: { playerId: sessionId, id: player.id }
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
this.sessions.get(sessionId)!.controllableEntities.add(player.id);
|
||||
}
|
||||
|
||||
private fetchHandler(
|
||||
req: Request,
|
||||
server: Server
|
||||
): Promise<Response> | Response {
|
||||
const url = new URL(req.url);
|
||||
|
||||
const headers = new Headers();
|
||||
headers.set('Access-Control-Allow-Origin', '*');
|
||||
|
||||
if (url.pathname == '/assign') {
|
||||
if (sessions.size > MAX_PLAYERS)
|
||||
if (this.sessions.size > Constants.MAX_PLAYERS)
|
||||
return new Response('too many players', { headers, status: 400 });
|
||||
|
||||
const sessionId = crypto.randomUUID();
|
||||
headers.set('Set-Cookie', `SessionId=${sessionId};`);
|
||||
|
||||
sessions.add(sessionId);
|
||||
|
||||
return new Response(sessionId, { headers });
|
||||
}
|
||||
|
||||
@ -127,7 +141,7 @@ const server = Bun.serve<SessionData>({
|
||||
}
|
||||
});
|
||||
|
||||
return new Response('upgraded', { headers });
|
||||
return new Response('upgraded to ws', { headers });
|
||||
}
|
||||
|
||||
if (url.pathname == '/me') {
|
||||
@ -135,67 +149,5 @@ const server = Bun.serve<SessionData>({
|
||||
}
|
||||
|
||||
return new Response('Not found', { headers, status: 404 });
|
||||
},
|
||||
websocket: {
|
||||
open(ws) {
|
||||
const { sessionId } = ws.data;
|
||||
|
||||
if (sessionControllableEntities.has(sessionId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const player = new Player(sessionId);
|
||||
game.addEntity(player);
|
||||
sessionControllableEntities.set(sessionId, new Set([player.id]));
|
||||
|
||||
messagePublisher.addMessage({
|
||||
type: MessageType.NEW_ENTITIES,
|
||||
body: [
|
||||
{
|
||||
entityName: EntityNames.Player,
|
||||
args: { playerId: sessionId, id: player.id }
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
ws.subscribe(GAME_TOPIC);
|
||||
},
|
||||
message(ws, message) {
|
||||
if (typeof message == 'string') {
|
||||
const receivedMessage = parse<ServerMessage>(message);
|
||||
receivedMessage.sessionData = ws.data;
|
||||
|
||||
messageReceiver.addMessage(receivedMessage);
|
||||
}
|
||||
},
|
||||
close(ws) {
|
||||
const { sessionId } = ws.data;
|
||||
|
||||
sessions.delete(sessionId);
|
||||
|
||||
const sessionEntities = sessionControllableEntities.get(sessionId);
|
||||
if (!sessionEntities) return;
|
||||
|
||||
messagePublisher.addMessage({
|
||||
type: MessageType.REMOVE_ENTITIES,
|
||||
body: Array.from(sessionEntities)
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
messagePublisher.setServer(server);
|
||||
|
||||
[
|
||||
new Physics(),
|
||||
new Collision(new Grid()),
|
||||
new WallBounds(),
|
||||
new NetworkUpdate(messageReceiver, messagePublisher, messageProcessor)
|
||||
].forEach((system) => game.addSystem(system));
|
||||
|
||||
game.start();
|
||||
setInterval(() => {
|
||||
game.doGameLoop(performance.now());
|
||||
}, SERVER_TICK_RATE);
|
||||
|
||||
console.log(`Listening on ${server.hostname}:${server.port}`);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user