generate uuids for entities; scaffolding for a server
This commit is contained in:
parent
2dc3120831
commit
732fe6f481
@ -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;
|
||||
|
@ -7,9 +7,9 @@ export class Game {
|
||||
private running: boolean;
|
||||
private lastTimeStamp: number;
|
||||
|
||||
public entities: Map<number, Entity>;
|
||||
public entities: Map<string, Entity>;
|
||||
public systems: Map<string, System>;
|
||||
public componentEntities: Map<string, Set<number>>;
|
||||
public componentEntities: Map<string, Set<string>>;
|
||||
|
||||
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<number>([entity.id]),
|
||||
new Set<string>([entity.id]),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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<string, Component>;
|
||||
|
||||
constructor() {
|
||||
this.id = Entity.ID++;
|
||||
this.id = crypto.randomUUID();
|
||||
this.components = new Map();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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<NetworkUpdateable>(
|
||||
ComponentNames.NetworkUpdateable,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
constructor() {
|
||||
this.game = new Game();
|
||||
|
||||
[
|
||||
new Physics(),
|
||||
new Collision({ width: Miscellaneous.WIDTH, height: Miscellaneous.HEIGHT }),
|
||||
new Collision({
|
||||
width: Miscellaneous.WIDTH,
|
||||
height: Miscellaneous.HEIGHT,
|
||||
}),
|
||||
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(() => {
|
||||
game.doGameLoop(performance.now());
|
||||
this.game.doGameLoop(performance.now());
|
||||
}, TICK_RATE);
|
||||
|
||||
const server = Bun.serve<>({
|
||||
port: 8080,
|
||||
fetch(req, server) {
|
||||
server.upgrade(req, {
|
||||
data: {},
|
||||
});
|
||||
},
|
||||
this.server = Bun.serve<any>({
|
||||
websocket: {
|
||||
// handler called when a message is received
|
||||
async message(ws, message) {
|
||||
console.log(`Received ${message}`);
|
||||
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);
|
||||
},
|
||||
},
|
||||
});
|
||||
console.log(`Listening on localhost:${server.port}`);
|
||||
}
|
||||
}
|
||||
|
||||
new Server();
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user