import { Runner } from '@pixi/runner';
import { EventEmitter } from '@pixi/utils';
import type { IRenderer } from '../IRenderer';
import type { ISystem, ISystemConstructor } from './ISystem';
interface ISystemConfig<R>
{
runners: string[],
systems: Record<string, ISystemConstructor<R>>
priority: string[];
}
/**
* The SystemManager is a class that provides functions for managing a set of systems
* This is a base class, that is generic (no render code or knowledge at all)
* @memberof PIXI
*/
export class SystemManager<R=IRenderer> extends EventEmitter
{
/** a collection of runners defined by the user */
readonly runners: {[key: string]: Runner} = {};
private _systemsHash: Record<string, ISystem> = {};
/**
* Set up a system with a collection of SystemClasses and runners.
* Systems are attached dynamically to this class when added.
* @param config - the config for the system manager
*/
setup(config: ISystemConfig<R>): void
{
this.addRunners(...config.runners);
// Remove keys that aren't available
const priority = (config.priority ?? []).filter((key) => config.systems[key]);
// Order the systems by priority
const orderByPriority = [
...priority,
...Object.keys(config.systems)
.filter((key) => !priority.includes(key))
];
for (const i of orderByPriority)
{
this.addSystem(config.systems[i], i);
}
}
/**
* Create a bunch of runners based of a collection of ids
* @param runnerIds - the runner ids to add
*/
addRunners(...runnerIds: string[]): void
{
runnerIds.forEach((runnerId) =>
{
this.runners[runnerId] = new Runner(runnerId);
});
}
/**
* Add a new system to the renderer.
* @param ClassRef - Class reference
* @param name - Property name for system, if not specified
* will use a static `name` property on the class itself. This
* name will be assigned as s property on the Renderer so make
* sure it doesn't collide with properties on Renderer.
* @returns Return instance of renderer
*/
addSystem(ClassRef: ISystemConstructor<R>, name: string): this
{
const system = new ClassRef(this as any as R);
if ((this as any)[name])
{
throw new Error(`Whoops! The name "${name}" is already in use`);
}
(this as any)[name] = system;
this._systemsHash[name] = system;
for (const i in this.runners)
{
this.runners[i].add(system);
}
/**
* Fired after rendering finishes.
* @event PIXI.Renderer#postrender
*/
/**
* Fired before rendering starts.
* @event PIXI.Renderer#prerender
*/
/**
* Fired when the WebGL context is set.
* @event PIXI.Renderer#context
* @param {WebGLRenderingContext} gl - WebGL context.
*/
return this;
}
/**
* A function that will run a runner and call the runners function but pass in different options
* to each system based on there name.
*
* E.g. If you have two systems added called `systemA` and `systemB` you could call do the following:
*
* ```js
* system.emitWithCustomOptions(init, {
* systemA: {...optionsForA},
* systemB: {...optionsForB},
* });
* ```
*
* `init` would be called on system A passing `optionsForA` and on system B passing `optionsForB`.
* @param runner - the runner to target
* @param options - key value options for each system
*/
emitWithCustomOptions(runner: Runner, options: Record<string, unknown>): void
{
const systemHashKeys = Object.keys(this._systemsHash);
runner.items.forEach((system) =>
{
// I know this does not need to be a performant function so it.. isn't!
// its only used for init and destroy.. we can refactor if required..
const systemName = systemHashKeys.find((systemId) => this._systemsHash[systemId] === system);
system[runner.name](options[systemName]);
});
}
/** destroy the all runners and systems. Its apps job to */
destroy(): void
{
Object.values(this.runners).forEach((runner) =>
{
runner.destroy();
});
this._systemsHash = {};
}
// TODO implement!
// removeSystem(ClassRef: ISystemConstructor, name: string): void
// {
// }
}