Created
September 27, 2022 15:31
-
-
Save Jimbly/b9b779961a3365a95c200aa8c622dbe5 to your computer and use it in GitHub Desktop.
Multiple inheritence class hierarchy for an entity manager
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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