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 { Game } from '@engine/Game';
|
||||||
import { EntityNames, Player } from '@engine/entities';
|
import { EntityNames, Player } from '@engine/entities';
|
||||||
import { WallBounds, Physics, Collision, NetworkUpdate } from '@engine/systems';
|
import { MessageType } from '@engine/network';
|
||||||
|
import { Constants } from './constants';
|
||||||
import {
|
import {
|
||||||
type MessageQueueProvider,
|
ServerSocketMessageReceiver,
|
||||||
type MessagePublisher,
|
ServerSocketMessagePublisher,
|
||||||
MessageType,
|
SessionData,
|
||||||
type MessageProcessor,
|
ServerMessage,
|
||||||
type Message
|
Session
|
||||||
} from '@engine/network';
|
} from './network';
|
||||||
import { stringify, parse } from '@engine/utils';
|
import { parse } from '@engine/utils';
|
||||||
import { Grid } from '@engine/structures';
|
import { Server, ServerWebSocket } from 'bun';
|
||||||
import { Miscellaneous } from '@engine/config';
|
|
||||||
import { Server } from 'bun';
|
|
||||||
|
|
||||||
const SERVER_PORT = 8080;
|
export class GameServer {
|
||||||
const SERVER_TICK_RATE = (1 / 60) * 1000;
|
private sessions: Map<string, Session>;
|
||||||
const GAME_TOPIC = 'game';
|
|
||||||
const MAX_PLAYERS = 8;
|
|
||||||
|
|
||||||
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 server?: Server;
|
||||||
private messages: Message[];
|
private game: Game;
|
||||||
|
private messageReceiver: ServerSocketMessageReceiver;
|
||||||
|
private messagePublisher: ServerSocketMessagePublisher;
|
||||||
|
|
||||||
constructor(server?: Server) {
|
constructor(
|
||||||
if (server) {
|
game: Game,
|
||||||
this.server = server;
|
messageReceiver: ServerSocketMessageReceiver,
|
||||||
|
messagePublisher: ServerSocketMessagePublisher
|
||||||
|
) {
|
||||||
|
this.sessions = new Map();
|
||||||
|
|
||||||
|
this.game = game;
|
||||||
|
this.messageReceiver = messageReceiver;
|
||||||
|
this.messagePublisher = messagePublisher;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.messages = [];
|
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 setServer(server: Server) {
|
private websocketMessage(
|
||||||
this.server = server;
|
websocket: ServerWebSocket<SessionData>,
|
||||||
}
|
message: string | Uint8Array
|
||||||
|
) {
|
||||||
|
if (typeof message == 'string') {
|
||||||
|
const receivedMessage = parse<ServerMessage>(message);
|
||||||
|
receivedMessage.sessionData = websocket.data;
|
||||||
|
|
||||||
public addMessage(message: Message) {
|
this.messageReceiver.addMessage(receivedMessage);
|
||||||
this.messages.push(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public publish() {
|
|
||||||
if (this.messages.length) {
|
|
||||||
this.server?.publish(GAME_TOPIC, stringify(this.messages));
|
|
||||||
|
|
||||||
this.messages = [];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private closeWebsocket(websocket: ServerWebSocket<SessionData>) {
|
||||||
|
const { sessionId } = websocket.data;
|
||||||
|
|
||||||
|
const sessionEntities = this.sessions.get(sessionId)!.controllableEntities;
|
||||||
|
|
||||||
|
this.sessions.delete(sessionId);
|
||||||
|
|
||||||
|
if (!sessionEntities) return;
|
||||||
|
this.messagePublisher.addMessage({
|
||||||
|
type: MessageType.REMOVE_ENTITIES,
|
||||||
|
body: Array.from(sessionEntities)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const game = new Game();
|
private openWebsocket(websocket: ServerWebSocket<SessionData>) {
|
||||||
|
websocket.subscribe(Constants.GAME_TOPIC);
|
||||||
|
|
||||||
const messageReceiver = new ServerSocketMessageReceiver();
|
const { sessionId } = websocket.data;
|
||||||
const messagePublisher = new ServerSocketMessagePublisher();
|
if (this.sessions.has(sessionId)) {
|
||||||
const messageProcessor = new ServerMessageProcessor();
|
return;
|
||||||
const sessionControllableEntities: Map<string, Set<string>> = new Map();
|
}
|
||||||
|
|
||||||
const sessions = new Set<string>();
|
this.sessions.set(sessionId, {
|
||||||
|
sessionId,
|
||||||
|
controllableEntities: new Set()
|
||||||
|
});
|
||||||
|
|
||||||
const server = Bun.serve<SessionData>({
|
const player = new Player(sessionId);
|
||||||
port: SERVER_PORT,
|
this.game.addEntity(player);
|
||||||
fetch: async (req, server): Promise<Response> => {
|
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 url = new URL(req.url);
|
||||||
|
|
||||||
const headers = new Headers();
|
const headers = new Headers();
|
||||||
headers.set('Access-Control-Allow-Origin', '*');
|
headers.set('Access-Control-Allow-Origin', '*');
|
||||||
|
|
||||||
if (url.pathname == '/assign') {
|
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 });
|
return new Response('too many players', { headers, status: 400 });
|
||||||
|
|
||||||
const sessionId = crypto.randomUUID();
|
const sessionId = crypto.randomUUID();
|
||||||
headers.set('Set-Cookie', `SessionId=${sessionId};`);
|
headers.set('Set-Cookie', `SessionId=${sessionId};`);
|
||||||
|
|
||||||
sessions.add(sessionId);
|
|
||||||
|
|
||||||
return new Response(sessionId, { headers });
|
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') {
|
if (url.pathname == '/me') {
|
||||||
@ -135,67 +149,5 @@ const server = Bun.serve<SessionData>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new Response('Not found', { headers, status: 404 });
|
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