Skip to content

Instantly share code, notes, and snippets.

@Jimbly
Created September 27, 2022 15:31
Show Gist options
  • Save Jimbly/b9b779961a3365a95c200aa8c622dbe5 to your computer and use it in GitHub Desktop.
Save Jimbly/b9b779961a3365a95c200aa8c622dbe5 to your computer and use it in GitHub Desktop.
Multiple inheritence class hierarchy for an entity manager
/**
* Conceptual/Code-time hierarchy:
*
* Sever Code Common Code Client Code
*
*
* EntityGameServer EntityGameClient
* Game Code | \ / |
* | EntityGameCommon |
* | | |
* | | |
* EntityBaseServer | EntityBaseClient
* Engine Code \ | /
* EntityBaseCommon
*
* Run-time hierarchies
* EntityGameServer EntityGameServer
* | |
* EntityGameCommon<T> EntityGameCommon<T>
* | |
* EntityBaseServer EntityBaseServer
* | |
* EntityBaseCommon EntityBaseCommon
*/
import assert from 'assert';
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
export type Constructor<T = {}> = new (...args: any[]) => T;
export type DataObject = Partial<Record<string, unknown>>;
// Engine Code : Common
export interface EntityManager<Entity extends EntityBaseCommon = EntityBaseCommon> {
entities: Partial<Record<number, Entity>>;
}
export class EntityBaseCommon {
manager: EntityManager;
id: number;
data: DataObject;
constructor(id: number, manager: EntityManager) {
this.id = id;
this.manager = manager;
this.data = {};
}
}
class ChannelWorker {
log(s: string) {
console.log(s);
}
}
export interface EntityManagerReadyWorker<Entity extends EntityBaseServer> extends ChannelWorker {
workerInitThing: (ent: Entity) => void;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type EntityManagerServerInterface = EntityManagerServer<any,any>;
// Engine Code : Server
export class EntityBaseServer extends EntityBaseCommon {
manager!: EntityManagerServerInterface;
server_field: number;
constructor(ent_id: number, entity_manager: EntityManagerServerInterface) {
super(ent_id, entity_manager);
this.server_field = 0;
}
serverFunc(id: number): number {
let other = this.manager.getEntity(id);
assert(other);
return this.server_field + other.server_field;
}
static loader<Entity extends EntityBaseServer>(sem: EntityManagerServerInterface, cb: (ent: Entity) => void): void {
let ent = sem.alloc();
sem.worker.log('foo');
cb(ent as Entity);
}
static cbs: Partial<Record<string, (this: EntityBaseServer, param: number) => void>> = {};
static register<Entity extends EntityBaseServer>(cmd: string, cb: (this: Entity, param: number) => void): void {
this.cbs[cmd] = cb as (this: EntityBaseServer, param: number) => void;
}
}
export class EntityManagerServer<
Entity extends EntityBaseServer,
Worker extends EntityManagerReadyWorker<Entity>
> implements EntityManager<Entity> {
entities: Partial<Record<number, Entity>> = {};
last_id = 0;
EntityCtor: typeof EntityBaseServer;
worker: Worker;
constructor(ctor: typeof EntityBaseServer, worker: Worker) {
this.EntityCtor = ctor;
this.worker = worker;
}
alloc(): Entity {
let ent = new this.EntityCtor(this.last_id++, this) as Entity;
this.entities[ent.id] = ent;
this.worker.workerInitThing(ent);
return ent;
}
allocPlayer(): void {
this.EntityCtor.loader(this, (ent: Entity) => {
this.entities[ent.id] = ent;
});
}
getEntity(id: number): Entity | undefined {
return this.entities[id];
}
dispatch(id: number, cmd: string, param: number): void {
let ent = this.getEntity(id);
assert(ent);
let cb = this.EntityCtor.cbs[cmd];
assert(cb);
cb.call(ent, param);
}
}
// Game code: Common
type GameDataObjectCommon = {
hp: number;
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function entityGameCommonClass<T extends Constructor<EntityBaseCommon>>(base: T) {
// Note: `base` is either `EntityBaseServer` or `EntityBaseClient`
return class EntityGameCommon extends base {
data!: GameDataObjectCommon;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(...args: any[]) {
super(...args);
this.data.hp = 10;
}
};
}
// Game code: Server
type GameDataObjectServer = {
dam: number;
};
class EntityGameServer extends entityGameCommonClass(EntityBaseServer) {
manager!: EntityManagerServer<EntityGameServer, GameWorker>;
data!: GameDataObjectServer & GameDataObjectCommon;
constructor(id: number, sem: EntityManagerServer<EntityGameServer, GameWorker>) {
super(id, sem);
this.data.hp = 1;
this.data.dam = 1;
}
attack(id: number) {
let other = this.manager.getEntity(id);
assert(other);
other.data.hp -= this.data.dam;
}
static loader = ((
sem: EntityManagerServer<EntityGameServer, GameWorker>,
cb: (ent: EntityGameServer) => void
) => {
let ent = sem.alloc();
sem.worker.log('foo');
ent.data.dam = 2;
cb(ent);
}) as typeof EntityBaseServer.loader;
}
EntityGameServer.register('hit', function (this: EntityGameServer, damage: number) {
this.data.hp -= damage;
this.manager.worker.playVFX('hit');
});
class GameWorker extends ChannelWorker implements EntityManagerReadyWorker<EntityGameServer> {
entity_manager: EntityManagerServer<EntityGameServer, GameWorker>;
constructor() {
super();
this.entity_manager = new EntityManagerServer(EntityGameServer, this);
this.entity_manager.worker.playVFX('startup');
}
workerInitThing(ent: EntityGameServer): void {
//
}
playVFX(key: string) {
//
}
}
let worker = new GameWorker();
let manager = worker.entity_manager;
let a = manager.alloc();
let b = manager.alloc();
a.attack(b.id);
manager.dispatch(a.id, 'hit', 7);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment