import { IEntity, State, IAction } from './state'; export type Handler = (value: any, value2?: any) => void; export interface ISystem { start: (state: State) => void; stop: () => void; add: (entity: IEntity) => void; handles: (...components: string[]) => boolean; remove: (entity: IEntity) => void; update: (entity: IEntity, component: string) => void; configuration: (value: any) => void; has: (entity: IEntity | string) => boolean; tick: (delta: number) => void; watch: (components: { [key: string]: Handler }) => void; } export const Config = (key: string) => (target: any, prop: string, description: PropertyDescriptor) => { target.constructor.config_handler = target.config_handler || {}; target.constructor.config_handler[key] = target[prop]; }; export const Component = (...keys: string[]) => (target: any, prop: string, description: PropertyDescriptor) => { keys.forEach(key => { target.constructor.component_handler = target.constructor.component_handler || {}; target.constructor.component_handler[key] = target[prop]; }); }; export interface ISystemConstructor { new(): ISystem; } export const Components = (...components: string[]) => (constructor: T) => { const base = (constructor as any).component_handler || {}; (constructor as any).component_handler = Object.assign(base, components.reduce((val, key) => { val[key] = null; return val; }, {})); }; export class System implements ISystem { protected component_handler: { [key: string]: Handler } = {}; protected config_handler: { [key: string]: Handler } = {}; protected config: any = {}; protected state: State; protected entities: { [key: string]: IEntity } = {}; constructor() { // grab whatever component handlers are on our prototype Object.assign(this.component_handler, (this.constructor as any).component_handler); Object.assign(this.config_handler, (this.constructor as any).config_handler); } handles(...components: string[]) { return components.some(component => this.component_handler[component] !== undefined); } watch(components: { [key: string]: Handler }) { Object.assign(this.component_handler, components); } configuration(config: any) { const previous = this.config; this.config = config; Object.keys(this.config_handler).forEach(key => { if (config[key] !== undefined || previous[key] !== undefined) { this.config_handler[key].call(this, config[key], previous[key]); } }); } start(state: State) { this.state = state; } stop() { // no-op } // probably want to change to look for fns by this.components update(entity: IEntity, component: string) { this.entities[entity.id] = entity; const updateFn = this.component_handler[component]; if (typeof updateFn === 'function') { updateFn.call(this, entity); } } add(entity: IEntity) { this.entities[entity.id] = entity; } remove(entity: IEntity) { delete this.entities[entity.id]; } has(entity: IEntity | string) { const key = typeof entity === 'object' ? entity.id : entity; return this.entities[key] !== undefined; } tick(delta: number) { // no-op } protected actions(actionMap: { [key: string]: (action: IAction) => void }) { Object.keys(actionMap).forEach(actionKey => { this.state.action(actionKey).forEach(action => actionMap[actionKey].call(this, action)); }); } protected dispatch(action: IAction, next = false) { action.from = this.constructor.name; this.state.dispatch(action, next); } }