<repo_name>ianpaschal/aurora<file_sep>/tests/core/Entity.test.ts
import { Component, Entity, System } from "../../src";
import uuid from "uuid";

let instance: Entity;
const config = {
	uuid: uuid(),
	type: "foo",
	name: "FooBar",
	components: [
		{ type: "foo", data: { isFoo: true } }
	]
};

beforeEach( () => {
	instance = new Entity( config );
});

// Constructor

describe( "Entity.constructor( config )", () => {
	it( "should copy the config's name if it exists.", () => {
		expect( instance.name ).toEqual( config.name );
	});
	it( "should copy the config's type if it exists.", () => {
		expect( instance.type ).toEqual( config.type );
	});
	it( "should copy the config's UUID if it exists.", () => {
		expect( instance.uuid ).toEqual( config.uuid );
	});
	it( "should instantiate components defined in config.", () => {
		expect( instance.hasComponent( "foo" ) ).toBe( true );
	});
});

// Getters

describe( "Entity.uuid", () => {
	it ( "should always be defined.", () => {
		expect( instance.uuid ).toBeDefined();
	});
});

describe( "Entity.json", () => {
	it( "should be a formatted JSON string representing the entity.", () => {
		expect( JSON.parse( instance.json ) ).toEqual({
			"destroy": false,
			"dirty": true,
			"uuid": config.uuid,
			"type": config.type,
			"name": config.name,
			"components": [
				{
					"data": { "isFoo": true },
					"type": "foo",
					"uuid": instance.getComponent( "foo" ).uuid
				}
			]
		});
	});
});

describe( "Entity.componentTypes", () => {
	it( "should return an array of component types.", () => {
		expect( instance.componentTypes ).toEqual( [ "foo" ] );
	});
});

// Other

describe( "Entity.addComponent( component )", () => {
	let component: Component;
	let length: number;
	beforeEach( () => {
		component = new Component();
		length = instance.components.length;
		instance.addComponent( component );
	});
	it( "should add the component if it doesn't exist already.", () => {
		expect( instance.components.length ).toEqual( length + 1 );
	});
	it( "should throw an error if that component is already added.", () => {
		expect( () => {
			instance.addComponent( component );
		}).toThrowError();
	});
});

describe( "Entity.clone()", () => {
	let clone: Entity;
	beforeEach( () => {
		clone = instance.clone();
	});
	it( "should not clone the UUID.", () => {
		expect( clone.uuid ).not.toEqual( instance.uuid );
	});
	it( "should clone the type.", () => {
		expect( clone.type ).toEqual( instance.type );
	});
	it( "should clone the data.", () => {
		for( let i = 0; i < instance.components.length; i++ ) {
			const original = instance.components[ i ];
			expect( clone.components[ i ].uuid ).not.toEqual( original.uuid );
			expect( clone.components[ i ].type ).toEqual( original.type );
			expect( clone.components[ i ].data ).toEqual( original.data );
		}
	});
});

describe( "Entity.copy( entity )", () => {
	let source: Entity;
	beforeEach( () => {
		source = new Entity({
			uuid: uuid(),
			type: "foo",
			components: []
		});
		instance.copy( source );
	});
	it( "should not copy the UUID.", () => {
		expect( instance.uuid ).not.toEqual( source.uuid );
	});
	it( "should copy the type.", () => {
		expect( instance.type ).toEqual( source.type );
	});
	it( "should copy the data.", () => {
		for( let i = 0; i < source.components.length; i++ ) {
			const original = source.components[ i ];
			expect( instance.components[ i ].uuid ).not.toEqual( original.uuid );
			expect( instance.components[ i ].type ).toEqual( original.type );
			expect( instance.components[ i ].data ).toEqual( original.data );
		}
	});
});

describe( "Entity.getComponentData( type )", () => {
	it( "should return the component data if it exists.", () => {
		expect( instance.getComponentData( "foo" ) ).toEqual({ isFoo: true });
	});
	it( "should throw an error if no component of that type exists.", () => {
		expect( () => {
			instance.getComponentData( "bar" );
		}).toThrowError();
	});
});

describe( "Entity.isWatchableBy( system )", () => {
	let systemFoo: System;
	let systemBar: System;
	beforeEach( () => {
		systemFoo = new System({
			componentTypes: [ "foo" ],
			name: "foo-system",
			onUpdate( t ) {}
		});
		systemBar = new System({
			componentTypes: [ "bar" ],
			name: "bar-system",
			onUpdate( t ) {}
		});
	});
	test( "should return true if the entity has all required components.", () => {
		expect( instance.isWatchableBy( systemFoo ) ).toBe( true );
	});
	test( "should return false if the entity is missing a component.", () => {
		expect( instance.isWatchableBy( systemBar ) ).toBe( false );
	});
});

describe( "Entity.removeComponent( type )", () => {
	it( "should remove the component if it exists.", () => {
		const length = instance.components.length;
		instance.removeComponent( "foo" );
		expect( instance.components.length ).toEqual( length - 1 );
	});
	it ( "should return the entity's component array.", () => {
		expect( instance.removeComponent( "foo" ) ).toEqual( [] );
	});
	it( "should throw an error if no component of that type exists.", () => {
		expect( () => {
			instance.removeComponent( "bar" );
		}).toThrowError();
	});
});

describe( "Entity.setComponentData( key, data )", () => {
	it( "should merge data into existing components with the given key.", () => {
		const data = {
			additionalProp: 7
		};
		instance.setComponentData( "foo", data );
		const component = instance.getComponent( "foo" );
		expect( Object.keys( component.data ) ).toContain( "additionalProp" );
		console.log( "data:", component.data );
		expect( component.data.additionalProp ).toEqual( data.additionalProp );
	});
	it( "should throw an error for components which are invalid/missing.", () => {
		expect( () => {
			instance.setComponentData( "bar", {});
		}).toThrowError();
	});
});
<file_sep>/dist/utils/interfaces.d.ts
import Entity from "../core/Entity";
/**
 * @module utils
 */
export interface ComponentConfig {
    data?: {} | [];
    type?: string;
    uuid?: string;
}
export interface EntityConfig {
    components?: any[];
    name?: string;
    type?: string;
    uuid?: string;
}
export interface SystemConfig {
    componentTypes: string[];
    fixed?: boolean;
    name: string;
    onAddEntity?: (entity: Entity) => void;
    onInit?: () => void;
    onRemoveEntity?: (entity: Entity) => void;
    onUpdate: (delta: number) => void;
    step?: number;
    methods?: {};
}
<file_sep>/tests/integration.test.ts
import { Engine, Entity, System, State } from "../src";

let engine: Engine;
let system: System;
let entityA: Entity;
let entityB: Entity;

beforeEach( () => {
	engine = new Engine();
	system = new System({
		componentTypes: [ "foo" ],
		name: "foo-system",
		onUpdate( t ) {}
	});
	entityA = new Entity({
		components: [
			{ type: "foo" }
		]
	});
	entityB = new Entity({
		components: [
			{ type: "bar" }
		]
	});
	engine.addSystem( system );
	engine.addEntity( entityA );
	engine.addEntity( entityB );
});

describe( "A state created from the engine", () => {
	let state: State;
	beforeEach( () => {
		engine.start();
		state = new State( engine, true );
	});
	it( "should have all entities added.", () => {
		expect( state.entities.length ).toBe( 2 );
	});
	// TODO: Ensure data is copied, not referenced
	it( "should have the correct timestamp.", () => {
		expect( state.timestamp ).toBeGreaterThan( 0 );
	});
});

describe( "A system added to the engine", () => {
	it( "should automatically watch compatible entities.", () => {
		expect( system.isWatchingEntity( entityA ) ).toBe( true );
	});
	it( "should not automatically watch incompatible entities.", () => {
		expect( system.isWatchingEntity( entityB ) ).toBe( false );
	});
	it( "should throw an error if attempting to modify component watch list.", () => {
		expect( () => {
			system.watchComponentType( "bar" );
		}).toThrowError();
	});
});
<file_sep>/dist/index.d.ts
export { default as Component } from "./core/Component";
export { default as Engine } from "./core/Engine";
export { default as Entity } from "./core/Entity";
export { default as State } from "./core/State";
export { default as System } from "./core/System";
export { default as capitalize } from "./utils/capitalize";
export { default as copy } from "./utils/copy";
export { default as getItem } from "./utils/getItem";
export { default as hasItem } from "./utils/hasItem";
<file_sep>/src/utils/interfaces.ts
// Aurora is distributed under the MIT license.

import Entity from "../core/Entity"; // Typing

/**
 * @module utils
 */
export interface ComponentConfig {
	data?:           {}|[];
	type?:           string;
	uuid?:           string;
}
export interface EntityConfig {
	components?:     any[];
	name?:           string,
	type?:           string,
	uuid?:           string,
}
export interface SystemConfig {
	componentTypes:  string[]
	fixed?:          boolean,
	name:            string,
	onAddEntity?:    ( entity: Entity ) => void,
	onInit?:         ()                 => void,
	onRemoveEntity?: ( entity: Entity ) => void,
	onUpdate:        ( delta: number )  => void,
	step?:           number,
	methods?:        {}
}
<file_sep>/dist/core/System.d.ts
import Engine from "./Engine";
import Entity from "./Entity";
import { SystemConfig } from "../utils/interfaces";
/**
 * @module core
 * @classdesc Class representing a system.
 */
export default class System {
    private _accumulator;
    private _componentTypes;
    private _engine;
    private _entityUUIDs;
    private _fixed;
    private _frozen;
    private _methods;
    private _name;
    private _onAddEntity;
    private _onInit;
    private _onRemoveEntity;
    private _onUpdate;
    private _step;
    /**
     * @description Create a System.
     * @param {Object} config - Configuration object
     * @param {string} config.name - System name
     * @param {boolean} config.fixed - Fixed step size or update as often as possible
     * @param {number} config.step - Step size in milliseconds (only used if `fixed` is `false`)
     * @param {array} config.componentTypes - Types to watch
     * @param {Function} config.onInit - Function to run when first connecting the system to the
     * engine
     * @param {Function} config.onAddEntity - Function to run on an entity when adding it to the
     * system's watchlist
     * @param {Function} config.onRemoveEntity - Function to run on an entity when removing it from
     * the system's watchlist
     * @param {Function} config.onUpdate - Function to run each time the engine updates the main loop
     */
    constructor(config: SystemConfig);
    /**
     * @description Get the accumulated time of the system.
     * @readonly
     * @returns {number} - Time in milliseconds
     */
    readonly accumulator: number;
    /**
     * @description Get whether or not the system uses a fixed step.
     * @readonly
     * @returns {boolean} - True if the system uses a fixed step
     */
    readonly fixed: boolean;
    /**
     * @description Get the step size of the system in milliseconds.
     * @readonly
     * @returns {number} - Time in milliseconds
     */
    readonly step: number;
    /**
     * @description Get the entity's name.
     * @readonly
     * @returns {string} - Name string
     */
    readonly name: string;
    /**
     * @description Get all of the component types the system is watching.
     * @readonly
     * @returns {string[]} - Array of component types
     */
    readonly watchedComponentTypes: string[];
    /**
     * @description Get all of the entity UUIDs the system is watching.
     * @readonly
     * @returns {string[]} - Array of UUID strings
     */
    readonly watchedEntityUUIDs: string[];
    /**
     * @description Add an extra method to the system. Cannot be modified after the system is
     * registered with the engine.
     * @param {string} key - Method identifier
     * @param {function} fn - Method to be called by user in the future
     */
    addMethod(key: string, fn: Function): void;
    /**
     * @description Check if the system can watch a given entity.
     * @readonly
     * @param {Entity} entity - Entity to check
     * @returns {boolean} - True if the given entity is watchable
     */
    canWatch(entity: Entity): boolean;
    /**
     * @description Call a user-added method from outside the system. Cannot be modified after the
     * system is registered with the engine.
     * @param {string} key - Method identifier
     * @param {any} payload - Any data which should be passed to the method
     * @returns {any} - Any data which the method returns
     */
    dispatch(key: string, payload?: any): any;
    /**
     * @description Initialize the system (as a part of linking to the engine). After linking the
     * engine, the system will run its stored init hook method. Cannot be modified after the system is
     * registered with the engine.
     * @param {Engine} engine - Engine instance to link to
     */
    init(engine: Engine): void;
    /**
     * @description Check if the system is watching a given component type.
     * @readonly
     * @param {Entity} entity - Component type to check
     * @returns {boolean} - True if the given component type is being watched
     */
    isWatchingComponentType(componentType: string): boolean;
    /**
     * @description Check if the system is watching a given entity.
     * @readonly
     * @param {Entity} entity - Entity instance to check
     * @returns {boolean} - True if the given entity instance is being watched
     */
    isWatchingEntity(entity: Entity): boolean;
    /**
     * @description Remove a user-added method from the system. Cannot be modified after the system is
     * registered with the
     * engine.
     * @param {string} key - Method identifier
     */
    removeMethod(key: string): void;
    /**
     * @description Remove a component type to the system's watch list. Cannot be modified after the
     * system is registered
     * with the engine.
     * @param {string} componentType - Component type to stop watching
     * @returns {array} - Array of watched component types
     */
    unwatchComponentType(componentType: string): string[];
    /**
     * @description Remove an entity UUID to the system's watch list.
     * @param {Entity} entity - Entity instance to stop watching
     * @returns {array} - Array of watched entity UUIDs
     */
    unwatchEntity(entity: Entity): string[];
    /**
     * @description Update the system with a given amount of time to simulate. The system will run its
     * stored update function using either a fixed step or variable step (specified at creation) and
     * the supplied delta time. Cannot be modified after the system is registered with the engine.
     * @param {number} delta - Time in milliseconds to simulate
     */
    update(delta: number): void;
    /**
     * @description Add a single component type to the system's watch list. Cannot be modified after
     * the system is registered with the engine.
     * @param {string} componentType - Component type to watch
     * @returns {array} - Array of watched component types
     */
    watchComponentType(componentType: string): string[];
    /**
     * @description Watch an entity by adding its UUID to to the system. After adding, the system will
     * run the entity through the internal add function to do any additional processing.
     * @param {Entity} entity - Entity instance to watch
     * @returns {array} - Array of watched entity UUIDs
     */
    watchEntity(entity: Entity): string[];
}
<file_sep>/dist/utils/hasItem.d.ts
/**
 * @module utils
 */
export default function hasItem(target: any, array: any, prop: any): boolean;
<file_sep>/dist/core/Entity.d.ts
import Component from "./Component";
import System from "./System";
import { EntityConfig } from "../utils/interfaces";
/**
 * @module core
 * @classdesc Class representing an entity.
 */
export default class Entity {
    private _components;
    private _destroy;
    private _dirty;
    private _name;
    private _type;
    private _uuid;
    /**
     * @description Create an entity. An object can be used when loading a previously created entity
     * from disk, or creating an entity to be used as an assembly to clone into new entity instances.
     * @param {Object} [config] - Configuration object
     * @param {string} [config.uuid] - Entity UUID
     * @param {string} [config.type] - Entity type
     * @param {string} [config.name] - Entity name (typically also called "unit type" in-game)
     * @param {Array} [config.components] - Array of component data objects to generate component
     * instances from
     */
    constructor(config?: EntityConfig);
    /**
     * @description Get all of the entity's component instances.
     * @readonly
     * @returns {Component[]} - Array of component instances
     */
    readonly components: Component[];
    /**
     * @description Get all of the entity's component types.
     * @readonly
     * @returns {string[]} - Array of component types
     */
    readonly componentTypes: string[];
    dirty: boolean;
    destroy: boolean;
    /**
     * @description Get the entity's data as a pure object (as compared to a class instance).
     * @readonly
     * @returns {Object} - Entity data as an object
     */
    readonly flattened: any;
    /**
     * @description Get the entity's data as a JSON string.
     * @readonly
     * @returns {string} - JSON string
     */
    readonly json: string;
    /**
     * @description Get the entity's name.
     * @readonly
     * @returns {string} - Name string
     */
    readonly name: string;
    /**
     * @description Get the entity's type.
     * @readonly
     * @returns {string} - Type string
     */
    readonly type: string;
    /**
     * @description Get the entity's UUID.
     * @readonly
     * @returns {string} - UUID string
     */
    readonly uuid: string;
    /**
     * @description Add a component instance to the entity. This method should only be called
     * internally, and never after the entity has been registered.
     * @private
     * @param {Component} component - The component to add
     * @returns {Component[]} - Updated array of components, or null if the component already existed
     */
    addComponent(component: Component): Component[];
    /**
     * @description Clone the entity.
     * @returns {Entity} - New Entity instance
     */
    clone(): Entity;
    /**
     * @description Copy another entity (such as an assembly) into the entity, replacing all
     * components.
     * @param {Entity} source - Entity to copy
     */
    copy(source: any): void;
    /**
     * @description Get a component instance by type from the entity.
     * @readonly
     * @param {string} type - Component type
     * @returns {Component} - Requested component instance
     */
    getComponent(type: string): Component;
    /**
     * @description Get data by component type from the entity. This is basically a shorthand for
     * .getComponent.getData();
     * @readonly
     * @param {string} type - Component type
     * @returns {any} - Requested component data
     */
    getComponentData(type: string): any;
    /**
     * @description Check if a component is present within the entity.
     * @readonly
     * @param {string} type - Component type
     * @returns {boolean} - True if the component is present
     */
    hasComponent(type: string): boolean;
    /**
     * @description Check if the entity is watchable by a given system.
     * @readonly
     * @param {System} system - System instance
     * @returns {boolean} - True if the entity is watchable
     */
    isWatchableBy(system: System): boolean;
    /**
     * @description Remove a component instance from the entity. This method should only be called
     * internally, and never after the entity has been registered.
     * @private
     * @param {string} type - Component type
     * @returns {Component[]} - Array of component instances
     */
    removeComponent(type: string): Component[];
    /**
     * @description Overwrite the data for a component of the given type within the entity.
     * @param {string} type - Component type
     * @param {Object} data - Data object
     */
    setComponentData(type: string, data: {}): void;
}
<file_sep>/CHANGELOG.md
# Changelog

## 2.2.0
- **ADDED:** Use `Entity.dirty` to keep track of entities which have had their component data changed. `Entity.setComponentData()` will automatically set that entity as dirty, while `Engine.cleanEntities()` will set all entities as no longer dirty. When you call that is up to you.
- **ADDED:** Use `Entity.flattened` to get an entity and all of its components as a pure object (vs. class instance). This property is now used internally by `Entity.json` as well.
- **ADDED:** In addition to specifying component type strings to define which components must be present on an entity to be watchable by a system, it is now possible to supply objects to define sets of components which are valid. See the documentation for details.

## 2.1.3
- **FIXED:** In system methods, `this` is now bound to the system instead of the method. This allows the methods to use system properties such as `this.entityUUIDs`.

## 2.1.2
- Patched security vulnerability by updating `merge` to version `1.2.1`.

## 2.1.1
- Patched security vulnerability by removing `parcel-bundler` from dependencies.
- **FIXED:** `eslint --fix` now actually runs on files (it did not previously).

## 2.1.0
- Total overhaul of Aurora with two major focuses:
    - Use TypeScript.
    - Be more of a framework and less of a boilerplate.
<file_sep>/dist/utils/copy.d.ts
/**
 * @module utils
 */
export default function copy(obj: any): any;
<file_sep>/dist/core/Component.d.ts
import { ComponentConfig } from "../utils/interfaces";
/**
 * @module core
 * @classdesc Class representing a component.
 */
export default class Component {
    private _data;
    private _type;
    private _uuid;
    /**
     * @description Create a Component.
     * @param {Object} [config] - Configuration object
     * @param {string} [config.uuid] - Component UUID
     * @param {string} [config.type] - Component type
     * @param {Object} [config.data] - Object containing data for the component
     */
    constructor(config?: ComponentConfig);
    /**
     * @description Get the component's data.
     * @readonly
     * @returns {Object} - The component's data
     */
    /**
    * @description Set the component's data. Note: This method differs from `.mergeData()` in that it
    * completely overwrites any existing data within the component.
    * @param {Object} data - Data object to apply
    * @returns {Object} - The component's updated updated data object
    */
    data: any;
    /**
     * @description Get the component's data as a JSON string.
     * @readonly
     * @returns {string} - The component's data as a JSON string
     */
    readonly json: string;
    /**
     * @description Get the component's type.
     * @readonly
     * @returns {string} - The component's type
     */
    /**
    * @description Set the component's type.
    * @param {string} type - New type for the component
    */
    type: string;
    /**
     * @description Get the component's UUID.
     * @readonly
     * @returns {String} - The component's UUID
     */
    readonly uuid: string;
    /**
     * @description Clone the component.
     * @returns {Component} - New component instance with the same data
     */
    clone(): Component;
    /**
     * @description Copy another component's data, resetting existing data.
     * @param {Component} source - Component to copy from
     */
    copy(source: Component): void;
    /**
     * @description Merge a data object into this component.
     * @param {Object} data - JSON data to apply to the component
     * @returns {(Object|Array)} - Updated data object/array
     */
    mergeData(data: any): any;
}
<file_sep>/tests/core/Engine.test.ts
import { Engine, Entity, System } from "../../src";

let instance: Engine;

beforeEach( () => {
	instance = new Engine();
});

// Constructor

describe( "Engine.constructor()", () => {
	it( "should create an empty assemblies array.", () => {
		expect( instance.assemblies ).toBeDefined();
		expect( Array.isArray( instance.assemblies ) ).toBe( true );
		expect( instance.assemblies.length ).toBe( 0 );
	});
	it( "should create an empty entities array.", () => {
		expect( instance.entities ).toBeDefined();
		expect( Array.isArray( instance.entities ) ).toBe( true );
		expect( instance.entities.length ).toBe( 0 );
	});
	it( "should create an empty systems array.", () => {
		expect( instance.systems ).toBeDefined();
		expect( Array.isArray( instance.systems ) ).toBe( true );
		expect( instance.systems.length ).toBe( 0 );
	});
	it( "should set last tick time as null.", () => {
		expect( instance.lastTickTime ).toBeDefined();
		expect( instance.lastTickTime ).toBe( null );
	});
	it( "should not have an .onUpdateStart() handler.", () => {
		expect( instance.onTickStart ).not.toBeDefined();
	});
	it( "should not have an .onUpdateComplete() handler.", () => {
		expect( instance.onTickComplete ).not.toBeDefined();
	});
});

// Adders

describe( "Engine.addAssembly( type )", () => {
	let assembly: Entity;
	beforeEach( () => {
		assembly = new Entity({
			type: "foo"
		});
		instance.addAssembly( assembly );
	});
	it( "should add the assembly to the engine.", () => {
		expect( instance.assemblies.length ).toBe( 1 );
	});
	it( "should throw an error if adding a duplicate assembly.", () => {
		expect( () => {
			instance.addAssembly( assembly );
		}).toThrowError();
	});
});

describe( "Engine.addEntity( entity )", () => {
	let entity: Entity;
	beforeEach( () => {
		entity = new Entity();
		instance.addEntity( entity );
	});
	it( "should add the entity to the engine.", () => {
		expect( instance.entities.length ).toBe( 1 );
	});
	it( "should throw an error if adding a duplicate entity.", () => {
		expect( () => {
			instance.addEntity( entity );
		}).toThrowError();
	});
});

describe( "Engine.addSystem( system )", () => {
	let system: System;
	beforeEach( () => {
		system = new System({
			name: "foo",
			componentTypes: [ "bar" ],
			onUpdate: function() {}
		});
		instance.addSystem( system );
	});
	it( "should add the system to the engine.", () => {
		expect( instance.systems.length ).toBe( 1 );
	});
	it( "should throw an error if adding a duplicate system.", () => {
		expect( () => {
			instance.addSystem( system );
		}).toThrowError();
	});
});

// Getters

describe( "Engine.getAssembly( type )", () => {
	it( "should retrive the correct assembly instance by type.", () => {
		const assembly = new Entity();
		instance.addAssembly( assembly );
		expect( instance.getAssembly( assembly.type ) ).toBe( assembly );
	});
	it( "should throw an error for invalid/missing types.", () => {
		expect( () => {
			instance.getAssembly( "bar" );
		}).toThrowError();
	});
});

describe( "Engine.getEntity( entity )", () => {
	it( "should retrive the correct entity instance by its UUID.", () => {
		const entity = new Entity();
		instance.addEntity( entity );
		expect( instance.getEntity( entity.uuid ) ).toBe( entity );
	});
	it( "should throw an error for invalid/missing UUIDs.", () => {
		expect( () => {
			instance.getEntity( "bar" );
		}).toThrowError();
	});
});

describe( "Engine.getSystem( name )", () => {
	it( "should retrive the correct system instance by name.", () => {
		const system = new System({
			name: "foo",
			componentTypes: [ "bar" ],
			onUpdate: function() {}
		});
		instance.addSystem( system );
		expect( instance.getSystem( system.name ) ).toBe( system );
	});
	it( "should throw an error for invalid/missing names.", () => {
		expect( () => {
			instance.getSystem( "bar" );
		}).toThrowError();
	});
});

// Checkers

describe( "Engine.hasAssembly( type )", () => {
	it( "should identify if an assembly has been added or not.", () => {
		const assembly = new Entity({
			type: "foo"
		});
		instance.addAssembly( assembly );
		expect( instance.hasAssembly( assembly.type ) ).toBe( true );
		expect( instance.hasAssembly( "bar" ) ).toBe( false );
	});
});

describe( "Engine.hasEntity( uuid )", () => {
	it( "should identify if an entity has been added or not.", () => {
		const entity = new Entity();
		instance.addEntity( entity );
		expect( instance.hasEntity( entity.uuid ) ).toBe( true );
		expect( instance.hasEntity( "bar" ) ).toBe( false );
	});
});

describe( "Engine.hasSystem( name )", () => {
	it( "should identify if a system has been added or not.", () => {
		const system = new System({
			name: "foo",
			componentTypes: [ "foo" ],
			onUpdate: function() {}
		});
		instance.addSystem( system );
		expect( instance.hasSystem( system.name ) ).toBe( true );
		expect( instance.hasSystem( "bar" ) ).toBe( false );
	});
});

// Other

describe( "Engine.start()", () => {
	let tickSpy;
	beforeEach( () => {
		tickSpy = jest.spyOn( Engine.prototype, "tick" );
	});
	it( ".should set the engine as running and trigger .tick().", () => {
		instance.start();
		expect( instance.running ).toBe( true );
	});
	it( "should only call .tick() once when no callback exists.", () => {
		expect( instance.onTickComplete ).not.toBeDefined();
		instance.start();
		expect( tickSpy ).toHaveBeenCalledTimes( 1 );
	});
	afterEach( () => {
		tickSpy.mockClear();
	});
});

describe( "Engine.stop()", () => {
	it( "should set the engine as not running.", () => {
		instance.stop();
		expect( instance.running ).toBe( false );
	});
});

describe( "Engine.tick()", () => {
	let handler;
	beforeEach( () => {
		handler = jest.fn();
	});
	it( "should call .onTickStart() if it's defined.", () => {
		instance.onTickStart = handler;
		instance.start();
		expect( instance.onTickStart ).toHaveBeenCalledTimes( 1 );
	});
	it( "should call .onTickComplete() if it's defined.", () => {
		instance.onTickComplete = handler;
		instance.start();
		expect( instance.onTickComplete ).toHaveBeenCalledTimes( 1 );
	});
});
<file_sep>/dist/utils/capitalize.d.ts
/**
 * @module utils
 */
export default function capitalize(str: any): any;
<file_sep>/tests/core/System.test.ts
import { Engine, Entity, System } from "../../src";

let instance: System;
let entityA: Entity;
let entityB: Entity;
const mockMethod = jest.fn();
const mockOnInit = jest.fn();
mockOnInit.mockReturnValue( true );
mockMethod.mockReturnValue( true );
const config = {
	name: "foo-system",
	step: 100,
	componentTypes: [ "foo", "bar" ],
	fixed: true,
	onUpdate( delta: number ) {},
	onInit: mockOnInit,
	methods: {
		"foo": mockMethod
	}
};

beforeEach( () => {
	instance = new System( config );
	entityA = new Entity({
		components: [
			{ type: "foo" },
			{ type: "bar" }
		]
	});
	entityB = new Entity({
		components: [
			{ type: "foo" }
		]
	});
});

// Constructor

describe( "System.constructor( config )", () => {
	it( "should set name from the config.", () => {
		expect( instance.name ).toBe( config.name );
	});
	it( "should set step size from the config.", () => {
		expect( instance.step ).toBe( config.step );
	});
	it ( "should set fixed step from config.", () => {
		expect( instance.fixed ).toBe( config.fixed );
	});
	it( "should watch the config's component list.", () => {
		expect( instance.isWatchingComponentType( "foo" ) ).toBe( true );
		expect( instance.isWatchingComponentType( "bar" ) ).toBe( true );
	});
	it( "should have at least one watched component type.", () => {
		expect( instance.watchedComponentTypes.length ).toBeGreaterThan( 0 );
	});
});

// Other

describe( "System.canWatch( entity )", () => {
	it( "should return true if all system component types are present.", () => {
		expect( instance.canWatch( entityA ) ).toBe( true );
	});
	it ( "should return false if any system component types are missing.", () => {
		expect( instance.canWatch( entityB ) ).toBe( false );
	});
});

describe( "System.dispatch( key )", () => {
	it( "should call the the user-defined method once.", () => {
		instance.dispatch( "foo" );
		expect( mockMethod.mock.calls.length ).toBe( 1 );
	});
});

describe( "System.init()", () => {
	// TODO: Test if it doesn't exist
	it( "should call its ._onInit handler function if it exists.", () => {
		instance.init( new Engine() );
		expect( mockOnInit.mock.calls.length ).toBe( 1 );
	});
});

describe( "System.isWatchingComponentType( type )", () => {
	it( "should identify if an assembly has been added or not.", () => {
		expect( instance.isWatchingComponentType( "foo" ) ).toBe( true );
		expect( instance.isWatchingComponentType( "not-present" ) ).toBe( false );
	});
});

describe( "System.removeMethod( key )", () => {
	it( "should remove the method with the given key if it exists.", () => {
		instance.removeMethod( "foo" );
		expect( () => {
			instance.dispatch( "foo", {});
		}).toThrowError();
	});
	it( "should throw an error if no method for that key exists.", () => {
		expect( () => {
			instance.removeMethod( "bar" );
		}).toThrowError();
	});
});

describe( "System.unwatchComponentType( type )", () => {
	it( "should remove the component type if it exists.", () => {
		const originalLength = instance.watchedComponentTypes.length;
		expect( () => {
			instance.unwatchComponentType( "foo" );
		}).not.toThrowError();
		expect( instance.watchedComponentTypes.length ).toBe( originalLength - 1 );
	});
	it( "should throw an error if less than one type is left.", () => {
		instance.unwatchComponentType( "foo" );
		expect( () => {
			instance.unwatchComponentType( "bar" );
		}).toThrowError();
	});
	it( "should throw an error if that component type does not exist.", () => {
		expect( () => {
			instance.unwatchComponentType( "not-present" );
		}).toThrow();
	});
});

describe( "System.unwatchEntity", () => {
	it( "should removable the entity with the given UUID.", () => {
		instance.watchEntity( entityA );
		const originalLength = instance.watchedEntityUUIDs.length;
		expect( () => {
			instance.unwatchEntity( entityA );
		}).not.toThrowError();
		expect( instance.watchedEntityUUIDs.length ).toBe( originalLength - 1 );
	});
	it( "should throw an error if that entity is not being watched.", () => {
		expect( () => {
			instance.unwatchEntity( new Entity() );
		}).toThrowError();
	});
});

describe( "System.update()", () => {
	it( "should save left over time in the accumulator.", () => {
		instance.update( 105 );
		expect( instance.accumulator ).toBe( 5 );
	});
});

describe( "System.watchComponentType( type )", () => {
	it( "should watch the component type.", () => {
		instance.watchComponentType( "new-type" );
		expect( instance.watchedComponentTypes ).toContain( "new-type" );
	});
	it( "should throw an error if watching a duplicate type.", () => {
		expect( () => {
			instance.watchComponentType( "foo" );
		}).toThrowError();
	});
});

describe( "System.watchEntity( entity )", () => {
	let entity: Entity;
	beforeEach( () => {
		entity = new Entity();
	});
	it( "should watch the entity.", () => {
		const originalLength = instance.watchedEntityUUIDs.length;
		instance.watchEntity( entity );
		expect( instance.watchedEntityUUIDs ).toContain( entity.uuid );
	});
	it( "should throw an error if watching a duplicate type.", () => {
		instance.watchEntity( entity );
		expect( () => {
			instance.watchEntity( entity );
		}).toThrowError();
	});
});
<file_sep>/src/core/State.ts
// Aurora is distributed under the MIT license.

import Engine from "./Engine"; // Typing
import Entity from "./Entity"; // Typing

/**
 * @module core
 * @classdesc Class representing a state.
 */
export default class State {

	private _timestamp: number;
	private _entities:  any[];

	/**
	 * @description Create a state instance from an engine.
	 * @param {Engine} engine - Engine instance
	 */
	constructor( engine: Engine, complete: boolean = false ) {
		this._timestamp = engine.lastTickTime;
		this._entities = [];
		engine.entities.forEach( ( entity ) => {

			// If not performing a full state capture and the entity is not dirty, skip it
			if ( !complete && !entity.dirty ) {
				return;
			}

			// Otherwise, flatten it to a JSON object and push it to the array
			this._entities.push( entity.flattened );
		});
		return this;
	}

	get flattened(): Object {
		return {
			timestamp: this._timestamp,
			entities: this._entities
		};
	}

	/**
	 * @description Get the state's entities.
	 * @readonly
	 * @returns {Entity[]} - Array of entity instances
	 */
	get entities(): Entity[] {
		return this._entities;
	}

	/**
	 * @description Get the state's timestamp in milliseconds.
	 * @readonly
	 * @returns {number} - Timestamp in milliseconds
	 */
	get timestamp(): number {
		return this._timestamp;
	}

	get json(): string {
		return JSON.stringify( this.flattened, null, 4 );
	}
}
<file_sep>/tests/utils/capitalize.test.ts
import { capitalize } from "../../src";

describe( "capitalize", () => {
	const str = "interloper";
	it( "should capitalize the first character of the string.", () => {
		expect( capitalize( str ) ).toEqual( "Interloper" );
	});
	it( "should return a new string.", () => {
		expect( capitalize( str ) ).not.toBe( str );
	});
});
<file_sep>/dist/core/State.d.ts
import Engine from "./Engine";
import Entity from "./Entity";
/**
 * @module core
 * @classdesc Class representing a state.
 */
export default class State {
    private _timestamp;
    private _entities;
    /**
     * @description Create a state instance from an engine.
     * @param {Engine} engine - Engine instance
     */
    constructor(engine: Engine, complete?: boolean);
    readonly flattened: Object;
    /**
     * @description Get the state's entities.
     * @readonly
     * @returns {Entity[]} - Array of entity instances
     */
    readonly entities: Entity[];
    /**
     * @description Get the state's timestamp in milliseconds.
     * @readonly
     * @returns {number} - Timestamp in milliseconds
     */
    readonly timestamp: number;
    readonly json: string;
}
<file_sep>/src/core/Engine.ts
// Aurora is distributed under the MIT license.

import present from "present";
import Entity from "./Entity";
import System from "./System";
import getItem from "../utils/getItem";
import hasItem from "../utils/hasItem";

/**
 * @module core
 * @classdesc Core singleton representing an instance of the Aurora engine. The engine is
 * responsible for the creation and registration of entities, as well as initialization and running
 * of systems containing game logic.
 */
export default class Engine {

	private _assemblies:     Entity[];
	private _entities:       Entity[];
	private _lastTickTime:   number;
	private _onTickComplete: Function; // Hook called when update is complete
	private _onTickStart:    Function; // Hook called when update starts
	private _running:        boolean;  // Whether or not the engine is running
	private _systems:        System[];

	/**
	 * @description Create an instance of the Aurora engine.
	 */
	constructor() {

		// TODO: Build from JSON in the case of loading a save

		console.log( "Aurora: Initializing a new engine." );

		// These are the things which are actually saved per game
		this._assemblies = [];
		this._entities = [];
		this._systems = [];

		// The heart of the engine
		this._running = false;
		this._lastTickTime = null;

		this._onTickStart = undefined;
		this._onTickComplete = undefined;

		return this;
	}

	/**
	 * @description Get all of the engine's assemblies.
	 * @readonly
	 * @returns {Entity[]} - Array of assembly (entity) instances
	 */
	get assemblies(): Entity[] {
		return this._assemblies;
	}

	/**
	 * @description Get all of the engine's entities.
	 * @readonly
	 * @returns {Entity[]} - Array of entity instances
	 */
	get entities(): Entity[] {
		return this._entities;
	}

	/**
	 * @description Get the function currently set to execute after every tick.
	 * @readonly
	 * @returns {Function} - Function currently set to execute
	 */
	get onTickComplete(): Function {
		return this._onTickComplete;
	}

	/**
	 * @description Get the function currently set to execute before every tick.
	 * @readonly
	 * @returns {Function} - Function currently set to execute
	 */
	get onTickStart(): Function {
		return this._onTickStart;
	}

	/**
	 * @description Get whether or not the engine is currently running.
	 * @readonly
	 * @returns {boolean} - True if the engine is running
	 */
	get running(): boolean {
		return this._running;
	}

	/**
	 * @description Get all of the engine's systems.
	 * @readonly
	 * @returns {System[]} - Array of system instances
	 */
	get systems(): System[] {
		return this._systems;
	}

	/**
	 * @description Get the timestamp of the engine's last tick.
	 * @readonly
	 * @returns {number} - Timestamp in milliseconds
	 */
	get lastTickTime() {
		return this._lastTickTime;
	}

	/**
	 * @description Set a function to execute after every update tick.
	 * @param {Function} fn - Function to execute
	 */
	set onTickComplete( fn: Function ) {
		this._onTickComplete = fn;
	}

	/**
	 * @description Set a function to execute before every update tick.
	 * @param {Function} fn - Function to execute
	 */
	set onTickStart( fn: Function ) {
		this._onTickStart = fn;
	}

	/**
	 * @description Add an assembly (entity) instance to the engine.
	 * @param {Entity} assembly - Assembly instance
	 * @returns {Entity[]} - Array of assembly (entity) instances
	 */
	addAssembly( assembly: Entity ): Entity[] {

		// Validate
		if ( this.hasAssembly( assembly.type ) ) {
			throw Error( "Assembly of that type has already been added!" );
		}

		// Freeze entity's structure
		Object.seal( assembly );

		this._assemblies.push( assembly );

		return this._assemblies;
	}

	/**
	 * @description Add an entity instance to the engine. This will check which systems should watch
	 * it, and add it to those systems (running the entity through each system's onAdd hook. After
	 * being added and initialized, entities are immutable (although their component data is not).
	 * @param {Entity} entity - Entity instance
	 * @returns {Entity[]} - Array of entity instances
	 */
	addEntity( entity: Entity ): Entity[] {

		// Validate
		if ( this.hasEntity( entity.uuid ) ) {
			throw Error( "Entity with that UUID has already been added!" );
		}

		// Freeze entity's structure
		Object.seal( entity );

		this._entities.push( entity );

		// Check all systems to see if they should be watching this entity
		this._systems.forEach( ( system ) => {
			if ( entity.isWatchableBy( system ) ) {
				system.watchEntity( entity );
			}
		});

		return this._entities;
	}

	/**
	 * @description Add a system instance to the engine.
	 *
	 * This will run the system's onInit hook. After being added and initialized, systems are
	 * immutable and are updated every game tick.
	 * @param {System} system - System instance
	 * @returns {System[]} - Array of system instances
	 */
	addSystem( system: System ): System[] {

		// Validate
		if ( this.hasSystem( system.name ) ) {
			throw Error( "System with that name has already been added!" );
		}

		// Add it and start it
		this._systems.push( system );
		system.init( this );

		// Freeze entity's structure
		Object.freeze( system );

		return this._systems;
	}

	/**
	 * @description Get an assembly (entity) instance by type from the engine.
	 * @readonly
	 * @param {string} type - Assembly type
	 * @returns {Entity} - Requested assembly (entity) instance
	 */
	getAssembly( type: string ): Entity {
		if ( !this.hasAssembly( type ) ) {
			throw Error( "No assembly of that type found!" );
		}
		return getItem( type, this._assemblies, "type" );
	}

	/**
	 * @description Get an entity instance by UUID from the engine.
	 * @readonly
	 * @param {string} uuid - Entity UUID
	 * @returns {Entity} - Requested entity instance
	 */
	getEntity( uuid: string ): Entity {
		if ( !this.hasEntity( uuid ) ) {
			throw Error( "No enitity with that UUID found!" );
		}
		return getItem( uuid, this._entities, "uuid" );
	}

	/**
	 * @description Get a system instance by name from the engine.
	 * @readonly
	 * @param {string} name - System name
	 * @returns {System} - Requested system instance
	 */
	getSystem( name: string ): System {
		if ( !this.hasSystem( name ) ) {
			throw Error( "No system with that name found!" );
		}
		return getItem( name, this._systems, "name" );
	}

	/**
	 * @description Check if an assembly is present within the engine.
	 * @readonly
	 * @param {string} name - Assembly name
	 * @returns {boolean} - True if the assembly is present
	 */
	hasAssembly( type: string ): boolean {
		return hasItem( type, this._assemblies, "type" );
	}

	/**
	 * @description Check if a system is present within the engine.
	 * @readonly
	 * @param {string} name - System name
	 * @returns {boolean} - True if the entity is present
	 */
	hasEntity( uuid: string ): boolean {
		return hasItem( uuid, this._entities, "uuid" );
	}

	/**
	 * @description Check if a system is present within the engine.
	 * @readonly
	 * @param {string} name - System name
	 * @returns {boolean} - True if the system is present
	 */
	hasSystem( name: string ): boolean {
		return hasItem( name, this._systems, "name" );
	}

	/**
	 * @description Start the execution of the update loop.
	 */
	start(): void {

		// Always reset in case engine was stopped and restarted
		this._lastTickTime = present();

		// Start ticking!
		this._running = true;
		this.tick();
	}

	/**
	 * @description Stop the execution of the update loop.
	 */
	stop(): void {
		this._running = false;
	}

	/**
	 * @description Perform one tick and update all systems.
	 *
	 * It is up to the user to decide how often to call this function. Typically on the client-side
	 * this would be called in requestAnimationFrame() and on the server-side this would simply be
	 * tied to a very fast setInterval() function (perhaps 10 ms).
	 */
	tick(): void {
		if ( this._running ) {
			const now = present();
			const delta = now - this._lastTickTime;
			this._lastTickTime = now;

			// Run any pre-update behavior
			if ( this._onTickStart ) {
				this._onTickStart();
			}

			// Perform the update on every system
			this._systems.forEach( ( system ) => {
				system.update( delta );
			});

			// Run any post-update behavior
			if ( this._onTickComplete ) {
				this._onTickComplete();
			}
		}
	}

	/**
	 * @description Set all entities to "clean".
	 *
	 * When this function is invoked is up to the developer. In some cases it makes sense to invoke it
	 * at the beginning of each tick, in other cases after all entities are sent from a server to the
	 * clients (for example).
	 */
	cleanEntities() {
		this._entities.forEach( ( entity ) => {
			entity.dirty = false;
		});
	}

}
<file_sep>/src/core/System.ts
// Aurora is distributed under the MIT license.

import Engine from "./Engine"; // Typing
import Entity from "./Entity"; // Typing
import { SystemConfig } from "../utils/interfaces"; // Typing

/**
 * @module core
 * @classdesc Class representing a system.
 */
export default class System {

	private _accumulator:    number;
	private _componentTypes: string[];
	private _engine:         Engine;
	private _entityUUIDs:    string[];
	private _fixed:          boolean;
	private _frozen:         boolean;
	private _methods:        {};
	private _name:           string;
	private _onAddEntity:    ( entity: Entity ) => void;
	private _onInit:         () => void;
	private _onRemoveEntity: ( entity: Entity ) => void;
	private _onUpdate:       ( delta: number ) => void;
	private _step:           number;

	/**
	 * @description Create a System.
	 * @param {Object} config - Configuration object
	 * @param {string} config.name - System name
	 * @param {boolean} config.fixed - Fixed step size or update as often as possible
	 * @param {number} config.step - Step size in milliseconds (only used if `fixed` is `false`)
	 * @param {array} config.componentTypes - Types to watch
	 * @param {Function} config.onInit - Function to run when first connecting the system to the
	 * engine
	 * @param {Function} config.onAddEntity - Function to run on an entity when adding it to the
	 * system's watchlist
	 * @param {Function} config.onRemoveEntity - Function to run on an entity when removing it from
	 * the system's watchlist
	 * @param {Function} config.onUpdate - Function to run each time the engine updates the main loop
	 */
	constructor( config: SystemConfig ) {

		// Define defaults
		this._accumulator    = 0;
		this._componentTypes = [];
		this._engine         = undefined;
		this._entityUUIDs    = [];
		this._fixed          = false;
		this._frozen         = false;
		this._methods        = {};
		this._name           = "no-name";
		this._onAddEntity    = ( entity: Entity ) => {};
		this._onInit         = () => {};
		this._onRemoveEntity = ( entity: Entity ) => {};
		this._onUpdate       = ( delta: number ) => {};
		this._step           = 100;

		// Apply config values
		Object.keys( config ).forEach( ( key ) => {

			// Handle component types and methods slightly differently, otherwise simply overwite props
			// with config values
			const specialCases = [ "componentTypes", "methods", "entityUUIDs" ];

			// If not a special case
			if ( specialCases.indexOf( key ) > -1 ) {
				switch( key ) {
					case "methods":
						Object.keys( config.methods ).forEach( ( key ) => {
							this.addMethod( key, config.methods[ key ] );
						});
						break;
					case "componentTypes":
						Object.keys( config.componentTypes ).forEach( ( key ) => {
							this.watchComponentType( config.componentTypes[ key ] );
						});
						break;
				}
			} else {
				this[ "_" + key ] = config[ key ];
			}
		});
	}

	/**
	 * @description Get the accumulated time of the system.
	 * @readonly
	 * @returns {number} - Time in milliseconds
	 */
	get accumulator(): number {
		return this._accumulator;
	}

	/**
	 * @description Get whether or not the system uses a fixed step.
	 * @readonly
	 * @returns {boolean} - True if the system uses a fixed step
	 */
	get fixed(): boolean {
		return this._fixed;
	}

	/**
	 * @description Get the step size of the system in milliseconds.
	 * @readonly
	 * @returns {number} - Time in milliseconds
	 */
	get step(): number {
		return this._step;
	}

	/**
	 * @description Get the entity's name.
	 * @readonly
	 * @returns {string} - Name string
	 */
	get name(): string {
		return this._name;
	}

	/**
	 * @description Get all of the component types the system is watching.
	 * @readonly
	 * @returns {string[]} - Array of component types
	 */
	get watchedComponentTypes(): string[] {
		return this._componentTypes;
	}

	/**
	 * @description Get all of the entity UUIDs the system is watching.
	 * @readonly
	 * @returns {string[]} - Array of UUID strings
	 */
	get watchedEntityUUIDs(): string[] {
		return this._entityUUIDs;
	}

	/**
	 * @description Add an extra method to the system. Cannot be modified after the system is
	 * registered with the engine.
	 * @param {string} key - Method identifier
	 * @param {function} fn - Method to be called by user in the future
	 */
	addMethod( key: string, fn: Function ): void {
		// TODO: Error handling
		this._methods[ key ] = fn.bind( this );
	}

	/**
	 * @description Check if the system can watch a given entity.
	 * @readonly
	 * @param {Entity} entity - Entity to check
	 * @returns {boolean} - True if the given entity is watchable
	 */
	canWatch( entity: Entity ): boolean {
		// TODO: Error handling
		// Faster to loop through search criteria vs. all components on entity
		for ( const type of this._componentTypes ) {

			// Return early if any required component is missing on entity
			if ( !entity.hasComponent( type ) ) {
				return false;
			}
		}
		return true;
	}

	/**
	 * @description Call a user-added method from outside the system. Cannot be modified after the
	 * system is registered with the engine.
	 * @param {string} key - Method identifier
	 * @param {any} payload - Any data which should be passed to the method
	 * @returns {any} - Any data which the method returns
	 */
	dispatch( key: string, payload?: any ): any {
		if ( !this._methods[ key ] ) {
			throw Error( `Method ${ key } does not exist!` );
		}
		return this._methods[ key ]( payload );
	}

	/**
	 * @description Initialize the system (as a part of linking to the engine). After linking the
	 * engine, the system will run its stored init hook method. Cannot be modified after the system is
	 * registered with the engine.
	 * @param {Engine} engine - Engine instance to link to
	 */
	init( engine: Engine ): void {
		console.log( "Initializing a new system: " + this._name + "." );
		this._engine = engine;

		// Run the actual init behavior:
		if ( this._onInit ) {
			this._onInit();
		}

		// Freeze the system to make it immutable:
		this._frozen = true;
	}

	/**
	 * @description Check if the system is watching a given component type.
	 * @readonly
	 * @param {Entity} entity - Component type to check
	 * @returns {boolean} - True if the given component type is being watched
	 */
	isWatchingComponentType( componentType: string ): boolean {
		if ( this._componentTypes.indexOf( componentType ) > -1 ) {
			return true;
		}
		return false;
	}

	/**
	 * @description Check if the system is watching a given entity.
	 * @readonly
	 * @param {Entity} entity - Entity instance to check
	 * @returns {boolean} - True if the given entity instance is being watched
	 */
	isWatchingEntity( entity: Entity ): boolean {
		if ( this._entityUUIDs.indexOf( entity.uuid ) > -1 ) {
			return true;
		}
		return false;
	}

	/**
	 * @description Remove a user-added method from the system. Cannot be modified after the system is
	 * registered with the
	 * engine.
	 * @param {string} key - Method identifier
	 */
	removeMethod( key: string ): void {
		if ( !this._methods[ key ] ) {
			throw Error( `Method ${ key } does not exist!` );
		}
		delete this._methods[ key ];
	}

	/**
	 * @description Remove a component type to the system's watch list. Cannot be modified after the
	 * system is registered
	 * with the engine.
	 * @param {string} componentType - Component type to stop watching
	 * @returns {array} - Array of watched component types
	 */
	unwatchComponentType( componentType: string ): string[] {
		const index = this._componentTypes.indexOf( componentType );
		if ( this._componentTypes.length < 2 ) {
			throw Error( "Cannot remove component type, this system will be left with 0." );
		}
		if ( index == -1 ) {
			throw Error( "Component type not found on system." );
		}
		this._componentTypes.splice( index, 1 );
		return this._componentTypes;
	}

	/**
	 * @description Remove an entity UUID to the system's watch list.
	 * @param {Entity} entity - Entity instance to stop watching
	 * @returns {array} - Array of watched entity UUIDs
	 */
	unwatchEntity( entity: Entity ): string[] {
		const index = this._entityUUIDs.indexOf( entity.uuid );
		if ( index < 0 ) {
			throw Error( `Could not unwatch entity ${ entity.uuid }; not watched.` );
		}
		this._entityUUIDs.splice( index, 1 );
		return this._entityUUIDs;
	}

	/**
	 * @description Update the system with a given amount of time to simulate. The system will run its
	 * stored update function using either a fixed step or variable step (specified at creation) and
	 * the supplied delta time. Cannot be modified after the system is registered with the engine.
	 * @param {number} delta - Time in milliseconds to simulate
	 */
	update( delta: number ): void {
		if ( this._fixed ) {
			// Add time to the accumulator & simulate if greater than the step size:
			this._accumulator += delta;
			while ( this._accumulator >= this._step ) {
				this._onUpdate( this._step );
				this._accumulator -= this._step;
			}
		} else {
			this._onUpdate( delta );
		}
	}

	/**
	 * @description Add a single component type to the system's watch list. Cannot be modified after
	 * the system is registered with the engine.
	 * @param {string} componentType - Component type to watch
	 * @returns {array} - Array of watched component types
	 */
	watchComponentType( componentType: string ): string[] {

		// Early return if frozen; this avoids updating the entity watch list during
		// execution.
		if ( this._frozen ) {
			throw Error( "Cannot modify watchedComponentTypes after adding to engine." );
		}

		// Check if this component type is already present
		if ( this._componentTypes.indexOf( componentType ) > -1 ) {
			throw Error( `Component type ${ componentType } is already being watched!` );
		}

		// If not, add it to the system
		this._componentTypes.push( componentType );
		return this._componentTypes;
	}

	/**
	 * @description Watch an entity by adding its UUID to to the system. After adding, the system will
	 * run the entity through the internal add function to do any additional processing.
	 * @param {Entity} entity - Entity instance to watch
	 * @returns {array} - Array of watched entity UUIDs
	 */
	watchEntity( entity: Entity ): string[] {

		// Check if this entity is already being watched
		if ( this._entityUUIDs.indexOf( entity.uuid ) >= 0 ) {
			throw Error( `Entity ${ entity.uuid } is already being watched!` );
		}
		this._entityUUIDs.push( entity.uuid );
		this._onAddEntity( entity );
		return this._entityUUIDs;
	}

}
<file_sep>/src/utils/hasItem.ts
// Aurora is distributed under the MIT license.

/**
 * @module utils
 */
export default function hasItem( target, array, prop ) {
	const match = array.find( ( item ) => {
		return item[ prop ] === target;
	});
	if ( !match ) {
		return false;
	}
	return true;
}
<file_sep>/dist/utils/getItem.d.ts
/**
 * @module utils
 */
export default function getItem(target: any, array: any, prop: any): any;
<file_sep>/dist/core/Engine.d.ts
import Entity from "./Entity";
import System from "./System";
/**
 * @module core
 * @classdesc Core singleton representing an instance of the Aurora engine. The engine is
 * responsible for the creation and registration of entities, as well as initialization and running
 * of systems containing game logic.
 */
export default class Engine {
    private _assemblies;
    private _entities;
    private _lastTickTime;
    private _onTickComplete;
    private _onTickStart;
    private _running;
    private _systems;
    /**
     * @description Create an instance of the Aurora engine.
     */
    constructor();
    /**
     * @description Get all of the engine's assemblies.
     * @readonly
     * @returns {Entity[]} - Array of assembly (entity) instances
     */
    readonly assemblies: Entity[];
    /**
     * @description Get all of the engine's entities.
     * @readonly
     * @returns {Entity[]} - Array of entity instances
     */
    readonly entities: Entity[];
    /**
     * @description Get the function currently set to execute after every tick.
     * @readonly
     * @returns {Function} - Function currently set to execute
     */
    /**
    * @description Set a function to execute after every update tick.
    * @param {Function} fn - Function to execute
    */
    onTickComplete: Function;
    /**
     * @description Get the function currently set to execute before every tick.
     * @readonly
     * @returns {Function} - Function currently set to execute
     */
    /**
    * @description Set a function to execute before every update tick.
    * @param {Function} fn - Function to execute
    */
    onTickStart: Function;
    /**
     * @description Get whether or not the engine is currently running.
     * @readonly
     * @returns {boolean} - True if the engine is running
     */
    readonly running: boolean;
    /**
     * @description Get all of the engine's systems.
     * @readonly
     * @returns {System[]} - Array of system instances
     */
    readonly systems: System[];
    /**
     * @description Get the timestamp of the engine's last tick.
     * @readonly
     * @returns {number} - Timestamp in milliseconds
     */
    readonly lastTickTime: number;
    /**
     * @description Add an assembly (entity) instance to the engine.
     * @param {Entity} assembly - Assembly instance
     * @returns {Entity[]} - Array of assembly (entity) instances
     */
    addAssembly(assembly: Entity): Entity[];
    /**
     * @description Add an entity instance to the engine. This will check which systems should watch
     * it, and add it to those systems (running the entity through each system's onAdd hook. After
     * being added and initialized, entities are immutable (although their component data is not).
     * @param {Entity} entity - Entity instance
     * @returns {Entity[]} - Array of entity instances
     */
    addEntity(entity: Entity): Entity[];
    /**
     * @description Add a system instance to the engine.
     *
     * This will run the system's onInit hook. After being added and initialized, systems are
     * immutable and are updated every game tick.
     * @param {System} system - System instance
     * @returns {System[]} - Array of system instances
     */
    addSystem(system: System): System[];
    /**
     * @description Get an assembly (entity) instance by type from the engine.
     * @readonly
     * @param {string} type - Assembly type
     * @returns {Entity} - Requested assembly (entity) instance
     */
    getAssembly(type: string): Entity;
    /**
     * @description Get an entity instance by UUID from the engine.
     * @readonly
     * @param {string} uuid - Entity UUID
     * @returns {Entity} - Requested entity instance
     */
    getEntity(uuid: string): Entity;
    /**
     * @description Get a system instance by name from the engine.
     * @readonly
     * @param {string} name - System name
     * @returns {System} - Requested system instance
     */
    getSystem(name: string): System;
    /**
     * @description Check if an assembly is present within the engine.
     * @readonly
     * @param {string} name - Assembly name
     * @returns {boolean} - True if the assembly is present
     */
    hasAssembly(type: string): boolean;
    /**
     * @description Check if a system is present within the engine.
     * @readonly
     * @param {string} name - System name
     * @returns {boolean} - True if the entity is present
     */
    hasEntity(uuid: string): boolean;
    /**
     * @description Check if a system is present within the engine.
     * @readonly
     * @param {string} name - System name
     * @returns {boolean} - True if the system is present
     */
    hasSystem(name: string): boolean;
    /**
     * @description Start the execution of the update loop.
     */
    start(): void;
    /**
     * @description Stop the execution of the update loop.
     */
    stop(): void;
    /**
     * @description Perform one tick and update all systems.
     *
     * It is up to the user to decide how often to call this function. Typically on the client-side
     * this would be called in requestAnimationFrame() and on the server-side this would simply be
     * tied to a very fast setInterval() function (perhaps 10 ms).
     */
    tick(): void;
    /**
     * @description Set all entities to "clean".
     *
     * When this function is invoked is up to the developer. In some cases it makes sense to invoke it
     * at the beginning of each tick, in other cases after all entities are sent from a server to the
     * clients (for example).
     */
    cleanEntities(): void;
}
<file_sep>/src/core/Component.ts
// Aurora is distributed under the MIT license.

import uuid from "uuid";
import copy from "../utils/copy";
import merge from "deepmerge";
import { ComponentConfig } from "../utils/interfaces"; // Typing

/**
 * @module core
 * @classdesc Class representing a component.
 */
export default class Component {

	private _data: any;
	private _type: string;
	private _uuid: string;

	/**
	 * @description Create a Component.
	 * @param {Object} [config] - Configuration object
	 * @param {string} [config.uuid] - Component UUID
	 * @param {string} [config.type] - Component type
	 * @param {Object} [config.data] - Object containing data for the component
	 */
	constructor( config?: ComponentConfig ) {

		// Define defaults
		this._uuid = uuid();
		this._type = "noname";
		this._data = {}; // NOTE: Some components use an array

		// Apply config values
		if ( config ) {
			Object.keys( config ).forEach( ( key ) => {

				// Handle data slightly differently, otherwise simply overwite props with config values
				if ( key === "data" ) {
					this._data = copy( config.data );
				} else {
					this[ "_" + key ] = config[ key ];
				}
			});
		}
	}

	// Expose properties

	/**
	 * @description Get the component's data.
	 * @readonly
	 * @returns {Object} - The component's data
	 */
	get data(): any {
		return this._data;
	}

	/**
	 * @description Set the component's data. Note: This method differs from `.mergeData()` in that it
	 * completely overwrites any existing data within the component.
	 * @param {Object} data - Data object to apply
	 * @returns {Object} - The component's updated updated data object
	 */
	set data( data: any ) {
		this._data = copy( data );
	}

	/**
	 * @description Get the component's data as a JSON string.
	 * @readonly
	 * @returns {string} - The component's data as a JSON string
	 */
	get json() {
		return JSON.stringify({
			data: this._data,
			type: this._type,
			uuid: this._uuid
		}, null, 4 );
	}

	/**
	 * @description Get the component's type.
	 * @readonly
	 * @returns {string} - The component's type
	 */
	get type(): string {
		return this._type;
	}

	/**
	 * @description Set the component's type.
	 * @param {string} type - New type for the component
	 */
	set type( type: string ) {
		this._type = type;
	}

	/**
	 * @description Get the component's UUID.
	 * @readonly
	 * @returns {String} - The component's UUID
	 */
	get uuid(): string {
		return this._uuid;
	}
	// Where is the set uuid() method? Doesn't exist! Don't change the UUID!

	// Other

	/**
	 * @description Clone the component.
	 * @returns {Component} - New component instance with the same data
	 */
	clone(): Component {
		const clone = new Component();
		clone.copy( this );
		return clone;
	}

	/**
	 * @description Copy another component's data, resetting existing data.
	 * @param {Component} source - Component to copy from
	 */
	copy( source: Component ): void {

		// Don't copy the UUID, only the type and data
		this._type = source.type;
		this._data = copy( source.data );
	}

	/**
	 * @description Merge a data object into this component.
	 * @param {Object} data - JSON data to apply to the component
	 * @returns {(Object|Array)} - Updated data object/array
	 */
	mergeData( data: any ): any {
		this._data = merge( this._data, data );
		return this._data;
	}

}
<file_sep>/README.md
<p>
	<img src="https://github.com/ianpaschal/aurora/blob/develop/logo.svg" />
</p>
<p>
	<a href="https://www.npmjs.com/package/aurora">
		<img src="https://img.shields.io/npm/v/aurora.svg" />
	</a>
	<a href="https://www.npmjs.com/package/aurora">
		<img src="https://img.shields.io/npm/dt/aurora.svg" />
	</a>
	<a href="https://github.com/ianpaschal/aurora/blob/master/LICENSE">
		<img src="https://img.shields.io/github/license/ianpaschal/aurora.svg" />
	</a>
	<a href="https://github.com/ianpaschal/aurora/issues">
		<img src="https://img.shields.io/github/issues-raw/ianpaschal/aurora.svg" />
	</a>
	<a href="https://codeclimate.com/github/ianpaschal/aurora">
		<img src="https://img.shields.io/travis/ianpaschal/aurora.svg?" />
	</a>
	<a href="https://codeclimate.com/github/ianpaschal/aurora">
		<img src="https://img.shields.io/codeclimate/maintainability/ianpaschal/aurora.svg?" />
	</a>
	<a href="https://codeclimate.com/github/ianpaschal/aurora">
		<img src="https://img.shields.io/codeclimate/coverage/ianpaschal/aurora.svg?" />
	</a>
	
</p>

Aurora is a small entity-component-system game engine for real-time-strategy games. It's also _sort of_ a mod API, but more on that later...

> Aurora is being developed alongside its intended use case, [Forge](https://github.com/ianpaschal/forge), and still churns quite a bit. There's also lots of test code present which requires refactoring and possible removal, but the basic architecture is stable and usable.

Aurora is based on four basic classes:

- `Engine`
- `Entity`
- `Component`
- `System`

In a nutshell:

- Components are JSON data with some useful helper methods.
- Entities are are arrays of components with some useful helper methods.
- Systems are pre-defined functions which are run each simulation loop and act on a subset of entities who have instances of the relevant components.
- The engine:
	- Tells all systems to update each game loop
	- Handles the creation and destruction of entities and components
	- Handles the loading of assets using `Three.js` (geometries, materials, etc.)

The [wiki](https://github.com/ianpaschal/aurora/wiki) contains more detailed information about the architecture and usage of Aurora and extensive documentation of the different classes and methods available can be found in [`/docs`](https://github.com/ianpaschal/aurora/tree/master/docs) or at [ianpaschal.github.io/aurora](https://ianpaschal.github.io/aurora).


## Mod API
> "I read something above that said Aurora is '_sort of_ a mod API'."

That's correct. With Aurora, there's not really a distinction between core content and mods. Because all entities are both represented and saved as JSON (plus whatever other image assets or sounds), it's easy to add new units to the game without actually having to `eval()` any Javascript. This is especially useful for modded multiplayer where the required mods can be safely sent to all players in the game.

Obviously this exempts "systems" from being added as _they_ are the actual executable functions which process a world's entities, but exchanging those should eventually be possible as well as the users' discretion.

Read more on the [wiki](https://github.com/ianpaschal/aurora/wiki).

## License
Aurora is licensed under the MIT license.
<file_sep>/tests/core/Component.test.ts
import { Component } from "../../src";
import uuid from "uuid";

let instance: Component;

beforeEach( () => {
	instance = new Component();
});

// Constructor

describe( "Component.constructor()", () => {
	// TODO: it should generate a valid UUID
	it( "should set type as 'noname'.", () => {
		expect( instance.type ).toBe( "noname" );
	});
	it( "should create an empty data object.", () => {
		expect( instance.data ).toEqual({});
	});
});

describe( "Component.constructor( config )", () => {
	let config;
	beforeEach( () => {
		config = {
			uuid: uuid(),
			type: "foo",
			data: {}
		};
		instance = new Component( config );
	});
	it( "should copy the config's uuid if it exists.", () => {
		expect( instance.uuid ).toEqual( config.uuid );
	});
	it( "should copy the config's type if it exists.", () => {
		expect( instance.type ).toEqual( config.type );
	});
	it( "should copy, not reference, the config's type if it exists.", () => {
		// Make sure to clone the original data, not reference it:
		expect( instance.data ).not.toBe( config.data );
		expect( instance.data ).toEqual( config.data );
	});
});

// Getters

describe( "Component.json", () => {
	it( "should return the correct JSON, without underscores.", () => {
		expect( instance.json ).toEqual( JSON.stringify({
			data: instance.data,
			type: instance.type,
			uuid: instance.uuid
		}, null, 4 ) );
	});
});

// Setters

describe( "Component.data = ", () => {
	it( "should copy, not reference, the new data, replacing everything.", () => {
		const data = {
			foo: "bar"
		};
		instance.data = data;
		expect( instance.data ).toEqual( data );
	});
});

describe( "Component.type = ", () => {
	it( "should set the type accordingly.", () => {
		const type = "foo";
		instance.type = type;
		expect( instance.type ).toEqual( type );
	});
});

// Other

describe( "Component.clone()", () => {
	let clone: Component;
	beforeEach( () => {
		clone = instance.clone();
	});
	it( "should not clone the UUID.", () => {
		expect( clone.uuid ).not.toEqual( instance.uuid );
	});
	it( "should clone the type.", () => {
		expect( clone.type ).toEqual( instance.type );
	});
	it( "should clone the data.", () => {
		// Ensure data is a copy, not a reference
		expect( clone.data ).not.toBe( instance.data );
		expect( clone.data ).toEqual( instance.data );
	});
});

describe( "Component.copy( component )", () => {
	let source: Component;
	beforeEach( () => {
		source = new Component({
			uuid: uuid(),
			type: "foo",
			data: {
				foo: 5
			}
		});
		instance.copy( source );
	});
	it( "should not copy the UUID.", () => {
		expect( instance.uuid ).not.toEqual( source.uuid );
	});
	it( "should copy the type.", () => {
		expect( instance.type ).toEqual( source.type );
	});
	it( "should copy the data.", () => {
		// Ensure data is a copy, not a reference
		expect( instance.data ).not.toBe( source.data );
		expect( instance.data ).toEqual( source.data );
	});
});

describe( "Component.mergeData( data )", () => {
	// TODO: Refine the tests for copying vs. overwriting data
	it( "should add missing data to the instance.", () => {
		instance.data = {
			foo: true
		};
		instance.mergeData({
			bar: 5
		});
		expect( instance.data ).toEqual({
			foo: true,
			bar: 5
		});
	});
	it( "should overwrite existing data on the instance.", () => {
		instance.data = {
			foo: true
		};
		instance.mergeData({
			foo: false
		});
		expect( instance.data ).toEqual({
			foo: false
		});
	});
});
<file_sep>/src/utils/copy.ts
// Aurora is distributed under the MIT license.

/**
 * @module utils
 */
export default function copy( obj ) {
	return JSON.parse( JSON.stringify( obj ) );
}
<file_sep>/src/utils/capitalize.ts
// Aurora is distributed under the MIT license.

/**
 * @module utils
 */
export default function capitalize( str ) {
	// TODO: Capitalize per word
	return str.charAt( 0 ).toUpperCase() + str.slice( 1 );
}
