From 732fe6f4811cc082bf938fed2d28c1f9c8bbd1f6 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Tue, 15 Aug 2023 18:30:19 -0600 Subject: [PATCH] generate uuids for entities; scaffolding for a server --- client/src/JumpStorm.ts | 57 +++++++++++++++++++- engine/Game.ts | 10 ++-- engine/components/NetworkUpdateable.ts | 8 ++- engine/entities/Entity.ts | 6 +-- engine/structures/QuadTree.ts | 12 ++--- engine/systems/NetworkUpdate.ts | 36 ++++++++++++- server/src/server.ts | 75 +++++++++++++++++--------- server/tsconfig.json | 33 +++++++----- 8 files changed, 176 insertions(+), 61 deletions(-) diff --git a/client/src/JumpStorm.ts b/client/src/JumpStorm.ts index bd48483..8075cc8 100644 --- a/client/src/JumpStorm.ts +++ b/client/src/JumpStorm.ts @@ -7,15 +7,63 @@ import { Physics, Input, Collision, + MessageQueueProvider, + MessagePublisher, + NetworkUpdate, } from "@engine/systems"; +class ClientSocketMessageQueueProvider implements MessageQueueProvider { + private socket: WebSocket; + private messages: any[]; + + constructor(socket: WebSocket) { + this.socket = socket; + this.messages = []; + + this.socket.addEventListener("message", (e) => { + console.log(e); + }); + } + + getNewMessages() { + return this.messages; + } + + clearMessages() { + this.messages = []; + } +} + +class ClientSocketMessagePublisher implements MessagePublisher { + private socket: WebSocket; + private messages: any[]; + + constructor(socket: WebSocket) { + this.socket = socket; + this.messages = []; + + this.socket.addEventListener("message", (e) => { + console.log(e); + }); + } + + addMessage(_message: any) {} + + publish() {} +} + export class JumpStorm { private game: Game; - private socket: WebSocket; constructor(ctx: CanvasRenderingContext2D) { this.game = new Game(); - this.socket = new WebSocket("ws://localhost:8080"); + + const socket = new WebSocket("ws://localhost:8080"); + const clientSocketMessageQueueProvider = + new ClientSocketMessageQueueProvider(socket); + const clientSocketMessagePublisher = new ClientSocketMessagePublisher( + socket, + ); [ this.createInputSystem(), @@ -23,6 +71,10 @@ export class JumpStorm { new Physics(), new Collision(), new WallBounds(ctx.canvas.width), + new NetworkUpdate( + clientSocketMessageQueueProvider, + clientSocketMessagePublisher, + ), new Render(ctx), ].forEach((system) => this.game.addSystem(system)); @@ -49,6 +101,7 @@ export class JumpStorm { inputSystem.keyPressed(e.key); } }); + window.addEventListener("keyup", (e) => inputSystem.keyReleased(e.key)); return inputSystem; diff --git a/engine/Game.ts b/engine/Game.ts index 07d06e8..8dc5db7 100644 --- a/engine/Game.ts +++ b/engine/Game.ts @@ -7,9 +7,9 @@ export class Game { private running: boolean; private lastTimeStamp: number; - public entities: Map; + public entities: Map; public systems: Map; - public componentEntities: Map>; + public componentEntities: Map>; constructor() { this.lastTimeStamp = performance.now(); @@ -29,11 +29,11 @@ export class Game { this.entities.set(entity.id, entity); } - public getEntity(id: number): Entity | undefined { + public getEntity(id: string): Entity | undefined { return this.entities.get(id); } - public removeEntity(id: number) { + public removeEntity(id: string) { this.entities.delete(id); } @@ -75,7 +75,7 @@ export class Game { if (!this.componentEntities.has(component.name)) { this.componentEntities.set( component.name, - new Set([entity.id]), + new Set([entity.id]), ); return; } diff --git a/engine/components/NetworkUpdateable.ts b/engine/components/NetworkUpdateable.ts index 73ceeba..980b064 100644 --- a/engine/components/NetworkUpdateable.ts +++ b/engine/components/NetworkUpdateable.ts @@ -1,7 +1,13 @@ import { Component, ComponentNames } from "."; export class NetworkUpdateable extends Component { - constructor() { + public isPublish: boolean; + public isSubscribe: boolean; + + constructor(isPublish: boolean, isSubscribe: boolean) { super(ComponentNames.NetworkUpdateable); + + this.isPublish = isPublish; + this.isSubscribe = isSubscribe; } } diff --git a/engine/entities/Entity.ts b/engine/entities/Entity.ts index ca8d314..b2d875d 100644 --- a/engine/entities/Entity.ts +++ b/engine/entities/Entity.ts @@ -1,13 +1,11 @@ import type { Component } from "../components"; export abstract class Entity { - private static ID = 0; - - public readonly id: number; + public readonly id: string; public readonly components: Map; constructor() { - this.id = Entity.ID++; + this.id = crypto.randomUUID(); this.components = new Map(); } diff --git a/engine/structures/QuadTree.ts b/engine/structures/QuadTree.ts index a57c6e7..49afdad 100644 --- a/engine/structures/QuadTree.ts +++ b/engine/structures/QuadTree.ts @@ -1,7 +1,7 @@ import type { Coord2D, Dimension2D } from "../interfaces"; interface BoxedEntry { - id: number; + id: string; dimension: Dimension2D; center: Coord2D; } @@ -72,8 +72,8 @@ export class QuadTree { } } - public getNeighborIds(boxedEntry: BoxedEntry): number[] { - const neighbors: number[] = this.objects.map(({ id }) => id); + public getNeighborIds(boxedEntry: BoxedEntry): string[] { + const neighbors: string[] = this.objects.map(({ id }) => id); if (this.hasChildren()) { this.getQuadrants(boxedEntry).forEach((quadrant) => { @@ -160,11 +160,7 @@ export class QuadTree { this.objects.forEach((boxedEntry) => { this.getQuadrants(boxedEntry).forEach((direction) => { const quadrant = this.children.get(direction); - quadrant?.insert( - boxedEntry.id, - boxedEntry.dimension, - boxedEntry.center, - ); + quadrant?.insert(boxedEntry); }); }); diff --git a/engine/systems/NetworkUpdate.ts b/engine/systems/NetworkUpdate.ts index dc7be20..6f8acb9 100644 --- a/engine/systems/NetworkUpdate.ts +++ b/engine/systems/NetworkUpdate.ts @@ -1,10 +1,42 @@ import { System, SystemNames } from "."; import { Game } from "../Game"; +import { ComponentNames, NetworkUpdateable } from "../components"; + +export interface MessageQueueProvider { + getNewMessages(): any[]; + clearMessages(): void; +} + +export interface MessagePublisher { + addMessage(message: any): void; + publish(): void; +} export class NetworkUpdate extends System { - constructor() { + private queueProvider: MessageQueueProvider; + private publisher: MessagePublisher; + + constructor( + queueProvider: MessageQueueProvider, + publisher: MessagePublisher, + ) { super(SystemNames.NetworkUpdate); + + this.queueProvider = queueProvider; + this.publisher = publisher; } - public update(_dt: number, _game: Game) {} + public update(_dt: number, game: Game) { + const messages = this.queueProvider.getNewMessages(); + this.queueProvider.clearMessages(); + + game.forEachEntityWithComponent( + ComponentNames.NetworkUpdateable, + (entity) => { + const networkUpdateComponent = entity.getComponent( + ComponentNames.NetworkUpdateable, + ); + }, + ); + } } diff --git a/server/src/server.ts b/server/src/server.ts index 74d901b..9a73f11 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -1,37 +1,60 @@ import { Game } from "../../engine/Game"; import { Floor, Player } from "../../engine/entities"; -import { WallBounds, Physics, Collision } from "../../engine/systems"; +import { + WallBounds, + Physics, + Collision, + MessageQueueProvider, + MessagePublisher, +} from "../../engine/systems"; import { Miscellaneous } from "../../engine/config"; const TICK_RATE = 60 / 1000; -const game = new Game(); +class Server { + private server: any; + private game: Game; -[ - new Physics(), - new Collision({ width: Miscellaneous.WIDTH, height: Miscellaneous.HEIGHT }), - new WallBounds(Miscellaneous.WIDTH), -].forEach((system) => game.addSystem(system)); + constructor() { + this.game = new Game(); -[new Floor(160), new Player()].forEach((entity) => game.addEntity(entity)); + [ + new Physics(), + new Collision({ + width: Miscellaneous.WIDTH, + height: Miscellaneous.HEIGHT, + }), + new WallBounds(Miscellaneous.WIDTH), + ].forEach((system) => this.game.addSystem(system)); -game.start(); -setInterval(() => { - game.doGameLoop(performance.now()); -}, TICK_RATE); + [new Floor(160), new Player()].forEach((entity) => + this.game.addEntity(entity), + ); -const server = Bun.serve<>({ - port: 8080, - fetch(req, server) { - server.upgrade(req, { - data: {}, + this.game.start(); + setInterval(() => { + this.game.doGameLoop(performance.now()); + }, TICK_RATE); + + this.server = Bun.serve({ + websocket: { + open(ws) { + ws.subscribe("the-group-chat"); + ws.publish("the-group-chat", msg); + }, + message(ws, message) { + // this is a group chat + // so the server re-broadcasts incoming message to everyone + ws.publish("the-group-chat", `${ws.data.username}: ${message}`); + }, + close(ws) { + const msg = `${ws.data.username} has left the chat`; + ws.unsubscribe("the-group-chat"); + ws.publish("the-group-chat", msg); + }, + }, }); - }, - websocket: { - // handler called when a message is received - async message(ws, message) { - console.log(`Received ${message}`); - }, - }, -}); -console.log(`Listening on localhost:${server.port}`); + } +} + +new Server(); diff --git a/server/tsconfig.json b/server/tsconfig.json index 29f8aa0..2567512 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -1,21 +1,28 @@ { "compilerOptions": { - "lib": ["ESNext"], + // add Bun type definitions + "types": ["bun-types"], + + // enable latest features + "lib": ["esnext"], "module": "esnext", "target": "esnext", + + // if TS 5.x+ "moduleResolution": "bundler", - "moduleDetection": "force", - "allowImportingTsExtensions": true, - "strict": true, - "downlevelIteration": true, - "skipLibCheck": true, - "jsx": "preserve", - "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": true, - "allowJs": true, "noEmit": true, - "types": [ - "bun-types" // add Bun global - ] + "allowImportingTsExtensions": true, + "moduleDetection": "force", + // if TS 4.x or earlier + "moduleResolution": "nodenext", + + "jsx": "react-jsx", // support JSX + "allowJs": true, // allow importing `.js` from `.ts` + "esModuleInterop": true, // allow default imports for CommonJS modules + + // best practices + "strict": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true } }