generate uuids for entities; scaffolding for a server

This commit is contained in:
Elizabeth Hunt 2023-08-15 18:30:19 -06:00
parent 2dc3120831
commit 732fe6f481
Signed by: simponic
GPG Key ID: 52B3774857EB24B1
8 changed files with 176 additions and 61 deletions

View File

@ -7,15 +7,63 @@ import {
Physics, Physics,
Input, Input,
Collision, Collision,
MessageQueueProvider,
MessagePublisher,
NetworkUpdate,
} from "@engine/systems"; } 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 { export class JumpStorm {
private game: Game; private game: Game;
private socket: WebSocket;
constructor(ctx: CanvasRenderingContext2D) { constructor(ctx: CanvasRenderingContext2D) {
this.game = new Game(); 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(), this.createInputSystem(),
@ -23,6 +71,10 @@ export class JumpStorm {
new Physics(), new Physics(),
new Collision(), new Collision(),
new WallBounds(ctx.canvas.width), new WallBounds(ctx.canvas.width),
new NetworkUpdate(
clientSocketMessageQueueProvider,
clientSocketMessagePublisher,
),
new Render(ctx), new Render(ctx),
].forEach((system) => this.game.addSystem(system)); ].forEach((system) => this.game.addSystem(system));
@ -49,6 +101,7 @@ export class JumpStorm {
inputSystem.keyPressed(e.key); inputSystem.keyPressed(e.key);
} }
}); });
window.addEventListener("keyup", (e) => inputSystem.keyReleased(e.key)); window.addEventListener("keyup", (e) => inputSystem.keyReleased(e.key));
return inputSystem; return inputSystem;

View File

@ -7,9 +7,9 @@ export class Game {
private running: boolean; private running: boolean;
private lastTimeStamp: number; private lastTimeStamp: number;
public entities: Map<number, Entity>; public entities: Map<string, Entity>;
public systems: Map<string, System>; public systems: Map<string, System>;
public componentEntities: Map<string, Set<number>>; public componentEntities: Map<string, Set<string>>;
constructor() { constructor() {
this.lastTimeStamp = performance.now(); this.lastTimeStamp = performance.now();
@ -29,11 +29,11 @@ export class Game {
this.entities.set(entity.id, entity); this.entities.set(entity.id, entity);
} }
public getEntity(id: number): Entity | undefined { public getEntity(id: string): Entity | undefined {
return this.entities.get(id); return this.entities.get(id);
} }
public removeEntity(id: number) { public removeEntity(id: string) {
this.entities.delete(id); this.entities.delete(id);
} }
@ -75,7 +75,7 @@ export class Game {
if (!this.componentEntities.has(component.name)) { if (!this.componentEntities.has(component.name)) {
this.componentEntities.set( this.componentEntities.set(
component.name, component.name,
new Set<number>([entity.id]), new Set<string>([entity.id]),
); );
return; return;
} }

View File

@ -1,7 +1,13 @@
import { Component, ComponentNames } from "."; import { Component, ComponentNames } from ".";
export class NetworkUpdateable extends Component { export class NetworkUpdateable extends Component {
constructor() { public isPublish: boolean;
public isSubscribe: boolean;
constructor(isPublish: boolean, isSubscribe: boolean) {
super(ComponentNames.NetworkUpdateable); super(ComponentNames.NetworkUpdateable);
this.isPublish = isPublish;
this.isSubscribe = isSubscribe;
} }
} }

View File

@ -1,13 +1,11 @@
import type { Component } from "../components"; import type { Component } from "../components";
export abstract class Entity { export abstract class Entity {
private static ID = 0; public readonly id: string;
public readonly id: number;
public readonly components: Map<string, Component>; public readonly components: Map<string, Component>;
constructor() { constructor() {
this.id = Entity.ID++; this.id = crypto.randomUUID();
this.components = new Map(); this.components = new Map();
} }

View File

@ -1,7 +1,7 @@
import type { Coord2D, Dimension2D } from "../interfaces"; import type { Coord2D, Dimension2D } from "../interfaces";
interface BoxedEntry { interface BoxedEntry {
id: number; id: string;
dimension: Dimension2D; dimension: Dimension2D;
center: Coord2D; center: Coord2D;
} }
@ -72,8 +72,8 @@ export class QuadTree {
} }
} }
public getNeighborIds(boxedEntry: BoxedEntry): number[] { public getNeighborIds(boxedEntry: BoxedEntry): string[] {
const neighbors: number[] = this.objects.map(({ id }) => id); const neighbors: string[] = this.objects.map(({ id }) => id);
if (this.hasChildren()) { if (this.hasChildren()) {
this.getQuadrants(boxedEntry).forEach((quadrant) => { this.getQuadrants(boxedEntry).forEach((quadrant) => {
@ -160,11 +160,7 @@ export class QuadTree {
this.objects.forEach((boxedEntry) => { this.objects.forEach((boxedEntry) => {
this.getQuadrants(boxedEntry).forEach((direction) => { this.getQuadrants(boxedEntry).forEach((direction) => {
const quadrant = this.children.get(direction); const quadrant = this.children.get(direction);
quadrant?.insert( quadrant?.insert(boxedEntry);
boxedEntry.id,
boxedEntry.dimension,
boxedEntry.center,
);
}); });
}); });

View File

@ -1,10 +1,42 @@
import { System, SystemNames } from "."; import { System, SystemNames } from ".";
import { Game } from "../Game"; 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 { export class NetworkUpdate extends System {
constructor() { private queueProvider: MessageQueueProvider;
private publisher: MessagePublisher;
constructor(
queueProvider: MessageQueueProvider,
publisher: MessagePublisher,
) {
super(SystemNames.NetworkUpdate); 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<NetworkUpdateable>(
ComponentNames.NetworkUpdateable,
);
},
);
}
} }

View File

@ -1,37 +1,60 @@
import { Game } from "../../engine/Game"; import { Game } from "../../engine/Game";
import { Floor, Player } from "../../engine/entities"; 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"; import { Miscellaneous } from "../../engine/config";
const TICK_RATE = 60 / 1000; const TICK_RATE = 60 / 1000;
const game = new Game(); class Server {
private server: any;
private game: Game;
constructor() {
this.game = new Game();
[ [
new Physics(), new Physics(),
new Collision({ width: Miscellaneous.WIDTH, height: Miscellaneous.HEIGHT }), new Collision({
width: Miscellaneous.WIDTH,
height: Miscellaneous.HEIGHT,
}),
new WallBounds(Miscellaneous.WIDTH), new WallBounds(Miscellaneous.WIDTH),
].forEach((system) => game.addSystem(system)); ].forEach((system) => this.game.addSystem(system));
[new Floor(160), new Player()].forEach((entity) => game.addEntity(entity)); [new Floor(160), new Player()].forEach((entity) =>
this.game.addEntity(entity),
);
game.start(); this.game.start();
setInterval(() => { setInterval(() => {
game.doGameLoop(performance.now()); this.game.doGameLoop(performance.now());
}, TICK_RATE); }, TICK_RATE);
const server = Bun.serve<>({ this.server = Bun.serve<any>({
port: 8080,
fetch(req, server) {
server.upgrade(req, {
data: {},
});
},
websocket: { websocket: {
// handler called when a message is received open(ws) {
async message(ws, message) { ws.subscribe("the-group-chat");
console.log(`Received ${message}`); 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);
}, },
}, },
}); });
console.log(`Listening on localhost:${server.port}`); }
}
new Server();

View File

@ -1,21 +1,28 @@
{ {
"compilerOptions": { "compilerOptions": {
"lib": ["ESNext"], // add Bun type definitions
"types": ["bun-types"],
// enable latest features
"lib": ["esnext"],
"module": "esnext", "module": "esnext",
"target": "esnext", "target": "esnext",
// if TS 5.x+
"moduleResolution": "bundler", "moduleResolution": "bundler",
"moduleDetection": "force",
"allowImportingTsExtensions": true,
"strict": true,
"downlevelIteration": true,
"skipLibCheck": true,
"jsx": "preserve",
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"allowJs": true,
"noEmit": true, "noEmit": true,
"types": [ "allowImportingTsExtensions": true,
"bun-types" // add Bun global "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
} }
} }