[
  {
    "path": ".github/workflows/build.yml",
    "content": "name: build\n\non:\n  workflow_dispatch:\n  push:\n    branches: [ $default-branch ]\n  pull_request:\n    branches: [ $default-branch ]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        node-version: [ 20 ]\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v4\n        with:\n          node-version: ${{ matrix.node-version }}\n          cache: 'yarn'\n\n      - name: Install dependencies\n        run: yarn install\n\n      - name: Build\n        run: yarn build\n\n      - name: Run tests\n        run: yarn test-ci\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish to NPM\n\non:\n  push:\n    branches:\n      - main\n      - master\n    tags:\n      - v*\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Use Node.js 20\n        uses: actions/setup-node@v4\n        with:\n          node-version: 20\n          cache: 'yarn'\n          registry-url: 'https://registry.npmjs.org'\n\n      - name: Install dependencies\n        run: yarn install\n\n      - name: Run tests\n        run: yarn test-ci\n\n  publish-to-npm:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node-version: [ 20 ]\n    needs: build\n    steps:\n      - uses: actions/checkout@v4\n      - name: Use Node.js 20\n        uses: actions/setup-node@v4\n        with:\n          node-version: 20\n          cache: 'yarn'\n          registry-url: 'https://registry.npmjs.org'\n\n      - name: Install dependencies\n        run: yarn install\n\n      - name: Build\n        run: yarn build\n\n      - name: Run tests\n        run: yarn test-ci\n\n      - name: Pack\n        run: yarn pack\n\n      - name: Publish to NPM\n        run: yarn publish --access public\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# TypeScript v1 declaration files\ntypings/\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n\n# next.js build output\n.next\n\n# Generated library\nlib"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\ndist: focal\nnode_js:\n  - \"19\"\nbefore_install:\n  - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.15.2\n  - export PATH=$HOME/.yarn/bin:$PATH\n\ninstall:\n  - yarn\n\ncache:\n  yarn: true\n\nbefore_script:\n  - yarn global add codecov\n  - yarn setup\n  - yarn build\n\nscript:\n  - yarn test-ci\n\nafter_success:\n  - codecov\n  - yarn pack\n\ndeploy:\n  - provider: npm\n    edge: true\n    cleanup: false\n    email: \"contact@pastila.org\"\n    api_key: $NPM_TOKEN\n    on:\n      tags: true\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# 4.3.0\n\nFeatures:\n\n- Introduced possibility to safely remove entities from the engine.\n  Now `Engine.removeEntity` takes a boolean value as a second argument \"safe\",\n  which indicates whether the entity should be removed safely or not.\n\n  If safe argument value is `true` then the entity will be removed after the Engine update cycle is\n  iteration is finished, meaning that the entity will be removed after all systems have been updated.\n\n  Safely removed entities won't be discoverable by getEntityById method, but they will be still accessible\n  in the queries (and remaining systems updates).\n\n  This behavior will become default in the next major release.\n\n# 4.2.0\n\nFeatures:\n\n- Now you can request for system removal when it's no longer needed. Check `requestRemoval`.\n\n# 4.1.0\n\nFixes:\n\n- \\[Breaking Change\\] Arguments order of `pick` by id API aligned with other APIs.\n- `isLinkedComponent` now returns false for undefined values, instead of throwing an Error.\n\n# 4.0.5\n\nFeatures:\n\n- The following APIs got an additional optional id parameter to make working with Linked Components easier: `has`\n  , `hasComponent`, `get`.\n\n# 4.0.4\n\nFixes:\n\n- `id` of `LinkedComponent` is not readonly anymore.\n\n# 4.0.3\n\nFeatures:\n\n- Linked components now have an optional id, and can be picked with `pick` by id.\n\nFixes:\n\n- Fixed usage sample for LinkedComponents\n\n# 4.0.2\n\nFixes:\n\n- If IterativeSystem was removed from the engine and added again later, no iteration took place.\n\n# 4.0.1\n\nFixes:\n\n- ReactionSystem now exported through index.ts\n\n# 4.0.0\n\nFeatures:\n\n- Added a new convenient API for working with linked components:\n  - Method `withdraw` removes the first LinkedComponent component of the provided type or existing standard component\n  - Method `pick` removes provided LinkedComponent component instance or existing standard component\n  - Method `iterate` iterates over instances of LinkedComponent and performs the `action` over each. Works for standard\n    components (action will be called for a single instance in this case).\n  - Method `find` searches a component instance of the specified class. Works for standard components (predicate will be\n    called for a single instance in this case).\n  - Method `getAll` returns a generator that can be used for iteration over all instances of specific type components.\n  - Method `lengthOf` returns the number of existing components of the specified class.\n\nBreaking changes:\n\n- Signals `onComponentAdded`, `onComponentRemoved` now will be triggered for every LinkedComponent.\n- Adding a linked component with `add` or `addComponent` will remove all existing linked components of the same type.\n  Linked components will be replaced even if the passed component already exists in the Entity.\n\n# 3.0.1\n\nFixes:\n\n- `EntitySnapshot.current` now is writable.\n- Added inline documentation to `EntitySnapshot.previous`.\n\n# 3.0.0\n\nFeatures:\n\n- Added shared config entity, that is accessible across all systems added to `Engine`\n- Added possibility to retrieve `Entity` from `Engine` by id\n\nBreaking changes:\n\n- Parameter `engine` was removed from `onAddedToEngine` and `onRemovedFromEngine` methods in the systems. Use `this.engine` instead.\n- `EntitySnapshot` was reimplemented. It has distinguished fields `EntitySnapshot.current and `EntitySnapshot.previous`,\n  which reflects current and previous Entity states accordingly.\n- `Entity.components` now represented as a `Record` instead of the `Map`\n\nImprovements:\n\n- Typed-signals was replaced with the built-in light-weight implementation.\n- `EntitySnapshot` won't be created if there are no change listeners.\n\nFixes:\n\n- `Entity.copyFrom` now copies tags.\n- `EntitySnapshot` now works properly with the tags. Previously, the difference between the previous state and the\n  current state did not show changes in the tags.\n- `EntitySnapshot` now works properly with the resolveClass.\n\n# 2.2.0\n\nFeatures:\n\n- Add linked components Fixed:\n- Documentation readability\n\n# 2.1.0\n\nFeatures:\n\n- Add possibility to set any type as the message type for subscription\n\n# 2.0.2\n\n- Fixed broken Class API\n\n# 2.0.1\n\n- Fixed broken API for QueryBuilder and Entity.remove\n\n# 2.0.0\n- Added tags support\n- Added messaging channel for system->engine->user\n- Fixed EntitySnapshot behavior\n- Added `engine` getter in the System\n- Added support of initialization ReactionSystem and IterativeSystem with QueryPredicate and QueryBuilder\n- Query got possibility to check whether entity is in it, via `has` method\n- Documentation completely rewritten\n\n# 1.4.1\n\n- Removed redundant `updateEntity` from `ReactionSystem`\n\n# 1.4.0\n\n- Added `ReactionSystem`\n- Documentation updated\n\n# 1.3.0\n\n- Fixed critical issue with updating of a `Query`. Queries whose predicates were a set of conditions that went beyond the capabilities of QueryBuilder could incorrectly evaluate the presence state for Entity after removing or adding components.   \n\n# 1.2.7\n\n- Fixed wrong type inference for `Entity#hasAll` and `Entity#hasAny`\n- Added several utility methods for `Query`\n\n# 1.2.6\n\n- Added `first`, `last` and `length` getter for queries\n\n# 1.2.5\n\n- Added feature of invalidation entity and queries\n- Fixed disconnecting of entities from engine\n\n# 1.2.4\n\n- Switched to commonjs modules\n\n# 1.2.3\n\n- Reverted `IterativeSystem#entities` remove\n- Added `IterativeSystem#prepare` protected method, which will be invoked after adding iterative system to engine\n\n# 1.2.2\n\n- Added Entity#hasAny, Entity#hasAll methods\n- Fixed throwing an error with passing invalid value to param `component` of `Entity#add` method\n- Removed redundant `entities` getter from `IterativeSystem`\n\n# 1.2.1\n\n- Fixed bug with disconnecting from Entity events after remove from Engine. \n- Added utility methods for clearing `Engine`. \n  - `Engine#clear()`\n  - `Engine#removeAllSystems()`\n  - `Engine#removeAllQueries()`\n  - `Engine#removeAllEntities()`\n\n# 1.2.0\n- Changed logic of resolving of component identifier. Changes could affect resolving of inherited components. Now inherited components will not be resolved as its ancestors.\n- Added parameter for Entity#add \"resolveClass\" - which specifies how could be resolved component.\n- Updated documentation\n- Added tests for Query#isEmpty \n\n# 1.1.2\n- Added Query#isEmpty property\n\n# 1.1.1\n- Added documentation\n\n# 1.1.0\n- Fixed query onEntityAdded, onEntityRemoved handlers\n- Added entity snapshot for properly handling of the entity changes\n\n# 1.0.7\n- Fixed false-positive query trigger\n\n# 1.0.6\n- Switched library target to ES5\n\n# 1.0.5\n- Updated documentation for every core type\n- Added guard that stops updating process for IterativeSystem, if it was removed engine\n- Fixed order of dispatching and removing of the component. Now dispatching happens before removing.\n- Added \"get accessor\" to query entities from Iterative system \n\n# 1.0.0\n- Initial release\n\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019-2020 Ilya Malanin\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Tick-Knock\n\n> Small and powerful, type-safe and easy-to-use Entity-Component-System (ECS)\n> library written in TypeScript\n\n[![Build Status](https://github.com/mayakwd/tick-knock/actions/workflows/build.yml/badge.svg)](https://travis-ci.org/mayakwd/tick-knock)\n[![Codecov Coverage](https://img.shields.io/codecov/c/github/mayakwd/tick-knock/develop.svg?style=flat-square)](https://codecov.io/gh/mayakwd/tick-knock/)\n\n😊 [Buy me a coffee](https://www.buymeacoffee.com/rdolivaw)\n\n# Table of contents\n\n- [Installing]\n- [How it works?]\n- [Inside the Tick-Knock]\n    - [Engine]\n        - [Subscription]\n    - [Component]\n    - [Linked Component]\n        - [Tag]\n        - [Entity]\n        - [System]\n        - [Query]\n            - [QueryBuilder]\n            - [Queries and Systems]\n            - [Built-in query-based systems]\n                - [ReactionSystem]\n                - [IterativeSystem]\n        - [Snapshot]\n        - [Shared Config]\n        - [Linked Components How-To]\n- [Restrictions]\n    - [Shared and Local Queries]\n    - [Queries with complex logic and Entity invalidation]\n- [License]\n- [Donation]\n\n# Installing\n\n- Yarn: `yarn add tick-knock`\n- NPM: `npm i --save tick-knock`\n\n# How it works?\n\nTick-Knock was inspired by several ECS libraries, mostly by [Ash ECS](https://www.richardlord.net/ash/).\n\nThe main approach was re-imagined to make it lightweight, easy-to-use, and less boiler-plate based.\n\n# Inside the Tick-Knock\n\nIn this part, you will learn all basics of Tick-Knock step by step.\n\n## Engine\n\nEngine is a \"world\" where entities, systems, and queries interact with each other.\n\nSince the Engine is the initial entry point for development with Tick-Knock, it is from this point that the creation of\nyour world starts. Usually, the Engine exists in just one instance, and it does nothing but orchestrating everything\nadded to it.\n\nTo begin with, you can add the most usual \"inhabitants\" to it.\n\n```typescript\nconst engine = new Engine();\nconst entity = new Entity()\n  .add(new Hero())\n  .add(new health(10))\nengine.addEntity(entity);\n```\n\nOr you can take it out:\n\n```typescript\nengine.removeEntity(entity);\n```\n\nThe second main \"inhabitant\" is System. It is responsible for processing Entities and their components. We will learn\nabout them in detail later.\n\n```typescript\nengine.addSystem(new ViewSystem(), 1);\nengine.addSystem(new PhysicsSystem(), 2);\n```\n\nAs you may have noticed, we pass two parameters: system instance, and the second is update priority. The higher the\npriority number is, the later the system will be processed.\n\nThe third type of resident is Query, which is responsible for mapping entities within the Engine and returns a list of\nalready filtered and ready-to-use entities.\n\n```typescript\nconst heroesQuery = new Query((entity) => entity.has(Hero));\nengine.addQuery(heroesQuery);\n````\n\nThe main task of the engine is to start the world update process and to report on the ongoing changes to Queries.  \nThese changes can be: additions to and removal of entities from the Engine, and changes in the components of specific\nEntities.\n\nTo perform the update step, we must call the `update` method and pass as a parameter the time elapsed since the previous\nupdate.  \nEvery time we start an update, the systems take turns, in order of priority, executing their own update methods.\n\n```typescript\n// Half a second has passed from the previous step.\nengine.update(0.5); \n```\n\n### Subscription\n\nAn additional - one of the Engine's responsibilities - transferring the messages from systems to the user. This can be\nvery useful when, for example, you want to report that the round in your game is over.\n\n```typescript\nengine.subscribe(GameOver, (message: GameOver) => {\n  if (game.win) {\n    this.showWinMessage();\n  } else {\n    this.showLoseMessage();\n  }\n});\n```\n\nYou can use not only class type as an argument but any value. For example, it could be a string or number.\n\n```typescript\nconst GAME_OVER = 'gameOver';\nengine.subscribe(GAME_OVER, () => {\n  this.showGameOver();\n});\n```\n\n> **Details of implementation**\n>\n> When the `dispatch` method is called in the system, then to get the right listeners, the compliance of\n> the `messageType` for each subscription will be checked.\n> - If `typeof subscription.messageType` is a `'function'`, then the matching will be performed using `instanceOf`.\n> - Otherwise, the matching will be done through strict equality `message === subscription.messageType`.\n\n## Component\n\nIt is a data object, its purpose - to represent a single aspect of your entity. For example, position, velocity,\nacceleration.\n\n- ❕ Any class could be considered as the component. There are no restrictions.\n- ❗ For proper understanding, it needs to be noticed that the component should be a data class, without any logic.\n  Otherwise, you'll lose the benefits of the ECS pattern.\n\n**Let's write your first component:**\n\n```typescript\nclass Position {\n  public constructor(\n    public x: number = 0,\n    public y: number = 0\n  ) {}\n}\n```\n\n> Yes, this is a component! 🎉\n\n## Linked component\n\nIt is still a data class, but it is made to solve the problem when you need to have multiple components of the same\ntype.\n\nLet's assume that you have a Damage component in your game. Several enemies attack the Hero simultaneously by adding the\nDamage component to it. What will happen? Only the last Damage component will be added to the Hero Entity because every\nprevious one will be removed.\n\nTo solve this problem - you need to implement ILinkedComponent interface in your Damage component and \"append\" instead\nof \"add\" the Damage component to the entity. That will do the job. After that, in DamageSystem you can find all damage\nsources:\n\n```typescript\nclass Damage extends LinkedComponent {\n  public constructor(\n    public readonly value: number\n  ) {\n    super()\n  }\n}\n\nhero.append(new Damage(100));\nhero.append(new Damage(5));\n\nclass DamageSystem extends IterativeSystem {\n  public constructor() {\n    super((entity) => entity.hasAll(Damage, Health));\n  }\n\n  public updateEntity(entity: Entity) {\n    const health = entity.get(Health)!;\n    while (entity.has(Damage)) {\n      const damage = entity.withdraw(Damage);\n      health.value -= damage.value;\n    }\n  }\n}\n```\n\n## Tag\n\nIt also can be called a \"label\". It's a simplistic way to help you not \"inflate\" your code with classes without data.\nFor instance, you want to mark your entity as Dead. There are two ways:\n\n- To create a component class: `class Dead {}`\n- Or to create a tag - that can be represented as a `string` or `number`.\n\nUsing tags is much easier and consumes less memory if you do not have additional component data.\n\n**Example:**\n\n```typescript\nconst ENEMY = 'enemy';\nconst HERO = 100500;\n```\n\n> Keep it simple! 😄\n\n## Entity\n\nIt is a general-purpose object, which can be marked with tags and can contain different components.\n\n- So it can be considered as a container that can represent any in-game entity, like an enemy, bomb, configuration, game\n  state, etc.\n- Entity can contain only one component or tag of each type. You can't add two `Position` components to the entity, the\n  second one will replace the first one.\n\n**This is how it works:**\n\n```typescript\nconst entity = new Entity()\n  .add(new Position(100, 100))\n  .add(new Position(200, 200))\n  .add(HERO);\n\nconsole.log(entity.get(Position)); // Position(x = 200, y = 200)\n```\n\n> Looks easy? Yes, it is!\n\n## System\n\nSystems are logic bricks in your application. If you want to manipulate entities, their components, and tags - it is the\nright place.\n\nPlease, keep in mind that the complexity of the system mustn't be too high. When you find that your system is doing too\nmuch in the \"update\" method, you need to split it into several systems.\n\nResponsibility of the system should cover no more than one logical aspect.\n\nThe system always has the following functionality:\n\n- Priority, which can be set before adding a system to the engine.\n- Reference to the `engine` will give you access to the engine itself and its entities. But be aware - you can't access\n  an engine if the system is not connected to it. Otherwise, you'll get an error.\n- Methods `onAddedToEngine` and `onRemovedFromEngine` will be called in the cases described by their naming.\n- With the method `dispatch`, you can easily send a message outside of the system. It will be delivered through the\n  engine [Subscription](#subscription) pipe. There are the same restrictions as for the engine. If the system is not\n  attached to the engine, then an attempt to send a message will throw an error.\n- And last but not least, the heart of your system - method `update`. It will be called whenever `Engine.update` is\n  being invoked. Update method - the right place to put your logic.\n\n**Example:**\nIt's time to write our first and straightforward system. It will iterate through all the entities that are in the\nEngine, check if they have Position and Velocity components.  \nAnd if they do, then move our object.\n\n```typescript\nclass Velocity {\n  public constructor(\n    public x: number = 0,\n    public y: number = 0\n  ) {}\n}\n\nclass PhysicsSystem extends System {\n  public constructor() {\n    super();\n  }\n\n  public update(dt: number): void {\n    const {entities} = this.engine;\n    for (const entity of entities) {\n      if (entity.hasAll(Position, Velocity)) {\n        const position = entity.get(Position)!;\n        const velocity = entity.get(Velocity)!;\n        position.x += velocity.x * dt;\n        position.y += velocity.y * dt;\n      }\n    }\n  }\n}\n```\n\n> There you go!\n> 🎁 In real life, you don't have to iterate through every entity in every system. It's completely uncomfortable and not\n> optimal. In this library, there is a mechanism that can prepare a list of the entities that you need according to the\n> criteria you set - it's called Query.\n\n## Query\n\nSo what the \"Query\" is? It's a matching mechanism that can tell you which entities in the Engine are suitable for your\nneeds.\n\nFor example, you want to write a system that is responsible for displaying sprites on your screen. To do this, you\nalways need a current list of entities, each of which has three components - View, Position, Rotation, and you want to\nexclude those marked with the HIDDEN tag.\n\n**Let's write our first Query.**\n\n```typescript\nconst displayListQuery = new Query((entity: Entity) => {\n  return entity.hasAll(View, Position, Rotation) && !entity.has(HIDDEN);\n});\n```\n\n> That's all!\n\nAdding this Query to the Engine will always contain an up-to-date list of entities that meet the described requirements.\nBesides, you can always find out when a new entity has appeared in the Query, or an old entity has left it.\n\n```typescript\ndisplayListQuery.onEntityAdded.connect(({current}: EntitySnapshot) => {\n  console.log(\"We've got a rookie here!\");\n  container.addChild(current.get(View)!.view);\n});\ndisplayListQuery.onEntityRemoved.connect(({previous}: EntitySnapshot) => {\n  container.removeChild(previous.get(View)!.view);\n  console.log(\"Good bye, friend!\");\n});\n```\n\n### QueryBuilder\n\nQuery builder is super simple. It has not much power, but you can use it for creating queries that must contain specific\nComponents.\n\n```typescript\nconst query: Query = new QueryBuilder()\n  .contains(ComponentA, ComponentB)\n  .contains(TAG)\n  .build();\n```\n\n### Queries and Systems\n\nNow let's see how we can use Query on systems?\n\nLet's write `ViewSystem`, which will be responsible for displaying our Entity on the screen.  \nWhen entities get to the list, the system will add them to the screen, and when they leave the list, the system will\nremove them from the screen.\n\n**Example:**\n\n```typescript\nconst query = new Query((entity: Entity) => {\n  return entity.hasAll(View, Position, Rotation) && !entity.has(HIDDEN);\n});\n\nclass ViewSystem extends System {\n  public constructor(\n    private readonly container: Container\n  ) { super(); }\n\n  public onAddedToEngine(): void {\n    // To make query work - we need to add it to the engine\n    this.engine.addQuery(query);\n    // And we need to add to the display list all entities that already \n    // exists in the Engine`s world and matches our Query \n    this.prepare();\n    // We want to know if new entities were added or removed\n    query.onEntityAdded.connect(this.onEntityAdded);\n    query.onEntityRemoved.connect(this.onEntityRemoved);\n  }\n\n  public onRemovedFromEngine(): void {\n    // There is no reason to update query after system was removed \n    // from the engine\n    this.engine.removeQuery(query);\n    // No reason for further listening of the updates\n    query.onEntityAdded.disconnect(this.onEntityAdded);\n    query.onEntityRemoved.disconnect(this.onEntityRemoved);\n  }\n\n  // We only want to update positions of the views on the screen,\n  // so there is no need for \"dt\" parameter, it can be omitted\n  public update(): void {\n    const entities = this.query.entities;\n    for (const entity of entities) {\n      this.updatePosition(entity);\n    }\n  }\n\n  private prepare(): void {\n    for (const entity of this.query.entities) {\n      this.onEntityAdded(entity);\n    }\n  }\n\n  private updatePosition(entity: Entity): void {\n    const {view} = entity.get(View)!;\n    const {x, y} = entity.get(Position)!;\n    const {rotation} = entity.get(Rotation)!;\n    view.position.set(x, y);\n    view.rotaion.set(rotation);\n  }\n\n  private onEntityAdded = ({current}: EntitySnapshot) => {\n    // Let's add new view to the screen\n    this.container.addChild(current.get(View)!.view);\n    // Don't forget to update it's position on the screen\n    this.updatePosition(current);\n  };\n\n  private onEntityRemoved = ({previous}: EntitySnapshot) => {\n    // Let's remove the view from the screen, because Entity no longer \n    // meets the requirements (might be it lost the View component \n    // or it was hidden)\n    this.container.removeChild(previous.get(View)!.view);\n  };\n}\n```\n\n> 😎 I'm sure you saw the reference to `EntitySnapshot` and wondering, \"what the heck is that?\". Please, be\n> patient, [I'll tell you about](#Snapshot) it a bit later.\n> I think it looks good and clear for understanding!\n\n- 🤔 You can say: \"we need to write too much boilerplate-code\".\n- And of course, Tick-Knock will help you to reduce boilerplate-code!\n\n### Built-in query-based systems\n\nIn favor of reducing the time to write the boilerplate code - Tick-Knock provides two built-in systems. Each of them\nalready knows how to work with Query, process the information coming from it, and allow access to this Query's entities.\n\nAll of the following built-in systems have the following features:\n\nYou can initialize those systems via three different items, which will be converted to Query eventually:\n\n- Query itself\n- Query predicate - Query will be automatically created on top of it. This feature was introduced to reduce the size of\n  the boilerplate code.\n- QueryBuilder - it is also a valid option.\n- They have a getter `entities`, which returns the current entities list of the Query.\n- They have a built-in property entityAdded and entityRemoved, you need to define them if you want to track Query\n  changes.\n\n#### ReactionSystem\n\nReactionSystem can be considered as the system that has the ability to react to changes in Query. It is a basic built-in\nsystem. Exactly it will be used in most cases when developing your application.\n\nLet's try to rewrite our ViewSystem, taking ReactionSystem as a basis, and take advantage of all the conveniences it\nprovides.\n\n**Example:**\n\n```typescript\nclass ViewSystem extends ReactionSystem {\n  public constructor(private readonly container: Container) {\n    super((entity: Entity) => {\n      return entity.hasAll(View, Position, Rotation) && !entity.has(HIDDEN);\n    });\n  }\n\n  public update(): void {\n    for (const entity of this.entities) {\n      this.updatePosition(entity);\n    }\n  }\n\n  protected prepare(): void {\n    for (const entity of this.entities) {\n      this.entityAdded(entity);\n    }\n  }\n\n  private updatePosition(entity: Entity): void {\n    const {view} = entity.get(View)!;\n    const {x, y} = entity.get(Position)!;\n    const {rotation} = entity.get(Rotation)!;\n    view.position.set(x, y);\n    view.rotaion.set(rotation);\n  }\n\n  protected entityAdded = ({current}: EntitySnapshot) => {\n    this.updatePosition(current);\n    this.container.addChild(current.get(View)!.view);\n  };\n\n  protected entityRemoved = ({previous}: EntitySnapshot) => {\n    this.container.removeChild(previous.get(View)!.view);\n  };\n}\n```\n\n> Now it's pretty simpler! 🎉\n\n#### IterativeSystem\n\nThis system has the same advantages as the ReactionSystem because it is inherited from the last one. 😅 All it brings is\na built-in iteration cycle for our Query inside the update method.\n\n**So, let's upgrade our `ViewSystem` a bit.**\n\n```typescript\nclass ViewSystem extends IterativeSystem {\n  // almost everything remains the same, so I'll skip most of the code.\n  // The only difference regarding example with ReactionSystem - that we \n  // don't need to override `update` method. \n  // Instead of it we need to override updateEntity method.\n  // Also, we can safely omit the dt parameter because we do not use it.\n  protected updateEntity(entity: Entity, dt: number) {\n    this.updatePosition(entity);\n  }\n}\n```\n\n#### Remove the system as it's done\n\nIt's possible to request removal of the system when you don't need it anymore. For example, the system is only\nneeded to render the playing field, and trying to run it at every update cycle is wasteful.\n\nFortunately, you can request deletion right from the system:\n\n```typescript\nclass RenderBoardSystem extends System {\n  public update(dt: number): void {\n    // Your render board code\n    this.requestRemoval();\n  }\n}\n```\n\nThat's it. Your system will be removed right after update cycle.\n\n## Snapshot\n\nAs you may have noticed, when we are tracking changes in Query, we get in `entityAdded` and `entityRemoved` not `Entity`\nbut `EntitySnapshot`.\n**So what is a snapshot?**\nIt is a container that displays the difference between the current state of Entity and its previous state. The `entity`\nproperty always reflects the current state. Still, methods ` get` and `has` methods of the snapshot return the data from\nthe previous state of the Entity before it was changed. So you can understand which components have been added and which\nhave been removed.\n\n> ❗ It is important to note that changes in the same entity components' data will not be reflected in the snapshot, even\n> if a manual invalidation of the entity has been triggered.\n\nSnapshots are very handy when you need to get a component or tag in Entity, but now it is missing. Let's take a closer\nlook at it with our `ViewSystem` example.\n**Example:**\n\n```typescript\nclass ViewSystem extends IterativeSystem {\n  // ...\n  protected entityAdded = ({current}: EntitySnapshot) => {\n    // When entity added to the Query that means that it has `View` \n    // component - one hundred percent! So we just need its current \n    // state. \n    this.container.addChild(current.get(View)!.view);\n    this.updatePosition(current);\n  };\n\n  protected entityRemoved = ({previous}: EntitySnapshot) => {\n    // But when entity removed - we can't be sure that current state \n    // of the entity has `View` component. So we need to get it from\n    // the previous state. Previous state has it one hundred percent.\n    this.container.removeChild(previous.get(View)!.view);\n  };\n  // ...\n}\n```\n\n## Shared Config\n\nIn real life, there is often a need to have a single Entity that acts as a configuration for the whole world.\n\nFor example, you have a set of complex systems that involve both game logic and visualization, and animations. But for\nfunctional test purposes - you don't care about the visuals and animations. You face the situation of passing a specific\nflag in each system during initialization, which will be responsible for disabling animation and visualization.\n\nNow imagine that you have several configuration parameters, and each of them you need to pass to all systems of your\nworld.\n\nTo simplify handling such situations - you can use `Engine.sharedConfig`. Shared Config is an `Entity` available in all\nsystems after adding them to `Engine`.\n\n**Example:**\n\n```typescript\nconst NO_VISUALS = 'no-visuals';\n\nclass ViewSystem extends IterativeSystem {\n  protected updateEntity(entity: Entity): void {\n    if (this.sharedConfig.has(NO_VISUALS)) {\n      return;\n    }\n\n    // Otherwise - update visuals\n  }\n}\n\nconst engine = new Engine();\nengine.sharedConfig.add(NO_VISUALS);\nengine.addSystem(new ViewSystem());\n```\n\n> ☝ Shared Config is the single instance connected to `Engine` since its initialization and can't be removed from it. It\n> affects queries like any regular `Entity`.\n\n## How to work with linked components?\n\nTick-knock provides an extended API for working with linked components since version 4.0.0.\n\n- Method `withdraw` removes the first LinkedComponent component of the provided type or existing standard component\n- Method `pick` removes provided LinkedComponent component instance or existing standard component.\n\n  **Example**\n  You have a system responsible for checking boons (buffs) expiration, and you wish to remove expired boons from the\n  hero:\n  ```ts\n  enum BoonType {\n    PROTECTION,\n    AEGIS,\n    REGENERATION\n  }\n\n  class Boon extends LinkedComponent {\n    public constructor(\n        public readonly type: BoonType,\n        public value: number,\n        public duration: number\n    ) { super(); }\n  }\n\n  class BoonExpirationTestSystem extends IterativeSystem {\n    public constructor() {\n      super((entity) => entity.has(Boon));\n    }\n    \n    public updateEntity(entity: Entity, dt: number) {\n      // Let's update all boons\n      entity.iterate(Boon, (boon) => {\n          // Let's reduce boon remaining duration\n          boon.duration -= dt;\n          // If boon is expired\n          if (boon.duration <= 0) {\n             // Then we need to removed it from the Entity\n             // But `entity.remove` will remove all boons, so we need to cherry-pick\n             entity.pick(boon);\n          } \n      });\n    }\n  }\n  ```\n- Method `iterate` iterates over instances of LinkedComponent and performs the `action` over each. Works for standard\n  components (action will be called for a single instance in this case).\n  > 🎈 It's safe to `pick` only current entity during iteration.\n- Method `find` searches a component instance of the specified class. Works for standard components (predicate will be\n  called for a single instance in this case).\n- Method `getAll` returns a generator that can be used for iteration over all instances of specific type components.\n- Method `lengthOf` returns the number of existing components of the specified class.\n\nNow you know the basics. Now let's look at some examples to help you understand when linked components are helpful and\nhow to work with them.\n\n### Real world example\n\nWe want to get a system that handles \"Regeneration\" buff on the hero. There can be more than one sources of\nregeneration, so we must handle all of them at the same time.\n\nRegeneration has two effects:\n\n- Instantly healing heroes by constant amount of health points\n- Regenerates some amount of health over the time.\n\nThus, our system should do the following:\n\n- Heal the hero on the adding every new Regeneration buff.\n- Heal the hero over the time.\n- Manages regeneration expiration.\n\n```ts\nclass Regeneration extends LinkedComponent {\n  public constructor(\n    public instantHealValue: number,\n    public healPerSecond: number,\n    public duration: number\n  ) { super(); }\n}\n\nclass RegenerationSystem extends IterativeSystem {\n  public constructor() {\n    super((entity) => entity.has(Hero, Regeneration));\n  }\n\n  public updateEntity(entity: Entity, dt: number) {\n    const hero = entity.get(Hero)!\n    // Let's update all regeneration components on our hero and apply their effects \n    entity.iterate(Regeneration, (it) => {\n      // We need to heal hero\n      const healthPointsToAdd = Math.ceil(it.healPerSecond * dt);\n      hero.health += healthPointsToAdd;\n      // And then reduce regeneration duration\n      it.duration -= dt;\n      // If it's expired\n      if (it.duration <= 0) {\n        // Then we need to removed it from the Entity\n        // But `entity.remove` will remove all boons, so we need to cherry-pick\n        entity.pick(it);\n      }\n    });\n  }\n\n  protected entityAdded = ({current}: EntitySnapshot) => {\n    // When new entity appears in the queue, that means that it has Hero and Regeneration\n    // so we want to instantly heal the hero by existing Regeneration buffs\n    current.iterate(Regeneration, (regeneration) => {\n      this.instantlyHealHero(entity, regeneration);\n    })\n    // Also, if any additional Regeneration buff will appear in the entity, we will handle \n    // them as well and instantly heal the hero\n    current.onComponentAdded.connect(this.instantlyHealHero);\n  }\n\n  protected entityRemoved = ({current}: EntitySnapshot) => {\n    // We don't want to know if any new components were added to the entity when it left \n    // the queue already.\n    current.onComponentAdded.disconnect(this.instantlyHealHero);\n  }\n\n  private instantlyHealHero = (entity: Entity, regeneration: any) => {\n    // We need to filter components, because this function will called on every added \n    // component (not only Regeneration)\n    if (!(regeneration instanceof Regeneration)) return;\n\n    const hero = entity.get(Hero)!;\n    hero.health += regeneration.instantHealValue;\n  }\n\n}\n```\n\n# Restrictions\n\n## Shared and Local Queries\n\nIn real development, you'll definitely face a situation when you want to reuse Query.\n\nFor example, when developing a game with heroes and enemies, you will surely always need two queries:\n\n**Simplified version**\n\n```typescript\nconst heroes = new Query(entity => entity.has(Hero));\nconst enemies = new Query(entity => entity.has(Enemy));\n```\n\nAnd you will want to use them in different systems. But the systems use local Queries. This means that after excluding a\nsystem from Engine, the Query in it will no longer be updated.\n\nTo prevent this from happening, you need to use the shared queries approach. To do this, you only need to add the query\nmanually after initializing the Engine.\n\n> shared-queries.ts\n\n```typescript\nexport const heroes = new Query(entity => entity.has(Hero));\nexport const enemies = new Query(entity => entity.has(Enemy));\n```\n\n```typescript\nimport {heroes, enemies} from 'shared-queries';\n// ...\nengine.addQuery(heroes);\nengine.addQuery(enemies)\n```\n\nNow you can use these Queries in any other system.\n\n**Example:**\n\n```typescript\nimport {heroes, enemies} from 'shared-queries';\n\nclass DamageSystem extends IterativeSystem {\n  // ...\n  protected updateEntity(entity: Entity) {\n    const damage = entity.remove(Damage)!\n    const isHero = heroes.has(entity);\n    if (damage.type === DamageType.SPLASH) {\n      const neighbours = getNeighbours(isHero ? heroes : enemies);\n      // ...\n    }\n  }\n}\n```\n\n## Queries with complex logic and Entity invalidation\n\nThere are limitations for Query that do not allow you to track changes made inside components automatically.\n\nSuppose that you want Query to track entities with an X position of 10.\n\n```typescript\nconst query = new Query((entity) => entity.has(Position) && entity.get(Position).x === 10);\n```\n\nAnd you have changed the Position parameters accordingly:\n\n```typescript\nentity.get(Position)!.x = 10;\n```\n\nThe query will not know about these changes because the mechanism for tracking changes in component fields is redundant\nand heavy, which will have a huge impact on performance. But to fix this, you can use an entity method\ncalled `invalidate`, it will force Query to check this particular entity.\n\n❗ Try not to use this approach too often. It may affect the performance of your application.\n\n# License\n\nThis software released under [MIT](https://github.com/Leopotam/ecs/blob/master/LICENSE.md) license! Good luck, folks.\n\n[Restrictions]: #restrictions\n\n[Shared Config]: #shared-config\n\n[Shared and Local Queries]: #shared-and-local-queries\n\n[Queries with complex logic and Entity invalidation]: #queries-with-complex-logic-and-entity-invalidation\n\n[Snapshot]: #snapshot\n\n[IterativeSystem]: #iterativesystem\n\n[ReactionSystem]: #reactionsystem\n\n[Built-in query-based systems]: #built-in-query-based-systems\n\n[Queries and Systems]: #queries-and-systems\n\n[QueryBuilder]: #querybuilder\n\n[Query]: #query\n\n[System]: #system\n\n[Entity]: #entity\n\n[Tag]: #tag\n\n[Component]: #component\n\n[Linked Component]: #linked-component\n\n[Linked Components How-To]: #how-to-work-with-linked-components\n\n[Installing]: #installing\n\n[How it works?]: #how-it-works\n\n[Inside the Tick-Knock]: #inside-the-tick-knock\n\n[Subscription]: #subscription\n\n[Engine]: #engine\n\n[License]: #license\n"
  },
  {
    "path": "jest-ci.json",
    "content": "{\n  \"transform\": {\n    \"^.+\\\\.tsx?$\": \"ts-jest\"\n  },\n  \"collectCoverage\": true,\n  \"moduleFileExtensions\": [\n    \"ts\",\n    \"js\"\n  ],\n  \"testMatch\": [\n    \"**/tests/unit/**/*.spec.(js|ts)|**/__tests__/*.(js|ts)\"\n  ],\n  \"transformIgnorePatterns\": [\n    \"/node_modules/\"\n  ],\n  \"moduleNameMapper\": {\n    \"^@/(.*)$\": \"<rootDir>/src/$1\"\n  }\n}"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"tick-knock\",\n  \"version\": \"4.3.0\",\n  \"description\": \"TypeScript Entity-Component-System library\",\n  \"author\": \"Ilya Malanin\",\n  \"license\": \"MIT\",\n  \"main\": \"lib/index.js\",\n  \"typings\": \"lib/index.d.ts\",\n  \"scripts\": {\n    \"setup\": \"yarn install\",\n    \"build\": \"tsc\",\n    \"build-watch\": \"tsc --watch\",\n    \"test\": \"jest\",\n    \"test-ci\": \"jest --config jest-ci.json\"\n  },\n  \"devDependencies\": {\n    \"@types/jest\": \"^29.2.4\",\n    \"@types/node\": \"^18.11.17\",\n    \"jest\": \"^29.3.1\",\n    \"ts-jest\": \"^29.0.3\",\n    \"ts-node\": \"^10.9.1\",\n    \"typescript\": \"^4.9.5\"\n  },\n  \"dependencies\": {\n    \"tslib\": \"^2.4.0\"\n  },\n  \"files\": [\n    \"lib\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mayakwd/tick-knock.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/mayakwd/tick-knock/issues\"\n  },\n  \"homepage\": \"https://github.com/mayakwd/tick-knock#readme\",\n  \"keywords\": [\n    \"ecs\",\n    \"entity\",\n    \"typescript\",\n    \"entity-component-system\",\n    \"gamedev\",\n    \"game\"\n  ],\n  \"jest\": {\n    \"transform\": {\n      \"^.+\\\\.tsx?$\": \"ts-jest\"\n    },\n    \"moduleFileExtensions\": [\n      \"ts\",\n      \"js\"\n    ],\n    \"testMatch\": [\n      \"**/tests/unit/**/*.spec.(js|ts)|**/__tests__/*.(js|ts)\"\n    ],\n    \"transformIgnorePatterns\": [\n      \"/node_modules/\"\n    ],\n    \"moduleNameMapper\": {\n      \"^@/(.*)$\": \"<rootDir>/src/$1\"\n    }\n  }\n}\n"
  },
  {
    "path": "src/ecs/ComponentId.ts",
    "content": "import {Class} from '../utils/Class';\n\n/**\n * Gets an id for a component class.\n *\n * @param component Component class\n * @param createIfNotExists - If `true` then unique id for class component will be created,\n *  in case if it wasn't assigned earlier\n */\nexport function getComponentId<T>(\n  component: Class<T>,\n  createIfNotExists: boolean = false,\n): number | undefined {\n  if (component.hasOwnProperty(COMPONENT_CLASS_ID)) {\n    return (component as ComponentId<T>)[COMPONENT_CLASS_ID];\n  } else if (createIfNotExists) {\n    return (component as ComponentId<T>)[COMPONENT_CLASS_ID] = componentClassId++;\n  }\n  return undefined;\n}\n\n/**\n * @internal\n */\nexport function getComponentClass<T extends K, K>(component: NonNullable<T>, resolveClass?: Class<K>) {\n  let componentClass = Object.getPrototypeOf(component).constructor as Class<T>;\n  if (resolveClass) {\n    if (!(component instanceof resolveClass || componentClass === resolveClass)) {\n      throw new Error('Resolve class should be an ancestor of component class');\n    }\n    componentClass = resolveClass as Class<T>;\n  }\n  return componentClass;\n}\n\nlet COMPONENT_CLASS_ID = '__componentClassId__';\nlet componentClassId: number = 1;\n\ntype ComponentId<T> = Class<T> & {\n  [key: string]: number;\n};\n\n"
  },
  {
    "path": "src/ecs/Engine.ts",
    "content": "import {Entity} from './Entity';\nimport {System} from './System';\nimport {Class} from '../utils/Class';\nimport {Query} from './Query';\nimport {Subscription} from './Subscription';\nimport {Signal} from '../utils/Signal';\n\n/**\n * Engine represents game state, and provides entities update loop on top of systems.\n */\nexport class Engine {\n  /**\n   * Signal dispatches when new entity were added to engine\n   */\n  public onEntityAdded: Signal<(entity: Entity) => void> = new Signal();\n  /**\n   * Signal dispatches when entity was removed from engine\n   */\n  public onEntityRemoved: Signal<(entity: Entity) => void> = new Signal();\n\n  private _entityMap: Map<number, Entity> = new Map();\n  private _entities: Entity[] = [];\n  private _systems: System[] = [];\n  private _queries: Query[] = [];\n  private _subscriptions: Subscription<any>[] = [];\n  private _sharedConfig: Entity = new Entity();\n  private _removalRequested: Set<number> = new Set();\n\n  /**\n   * Gets a list of entities added to engine\n   */\n  public get entities(): ReadonlyArray<Entity> {\n    return Array.from(this._entities);\n  }\n\n  /**\n   * Gets a list of systems added to engine\n   */\n  public get systems(): ReadonlyArray<System> {\n    return this._systems;\n  }\n\n  /**\n   * Gets a list of queries added to engine\n   */\n  public get queries(): ReadonlyArray<Query> {\n    return this._queries;\n  }\n\n  public constructor() {\n    this.connectEntity(this._sharedConfig);\n  }\n\n  /**\n   * @internal\n   */\n  public get subscriptions(): ReadonlyArray<Subscription<any>> {\n    return this._subscriptions;\n  }\n\n  /**\n   * Gets a shared config entity, that's accessible from every system added to engine\n   *\n   * @return {Entity}\n   */\n  public get sharedConfig(): Entity {\n    return this._sharedConfig;\n  }\n\n  /**\n   * Adds an entity to engine.\n   * If entity is already added to engine - it does nothing.\n   *\n   * @param entity Entity to add to engine\n   * @see onEntityAdded\n   */\n  public addEntity(entity: Entity): Engine {\n    if (this._entityMap.has(entity.id)) {\n      this._removalRequested.delete(entity.id);\n      return this;\n    }\n    this._entities.push(entity);\n    this._entityMap.set(entity.id, entity);\n    this.onEntityAdded.emit(entity);\n    this.connectEntity(entity);\n    return this;\n  }\n\n  /**\n   * Remove entity from engine\n   * If engine not contains entity - it does nothing.\n   *\n   * @param entity Entity to remove from engine\n   * @param safe If true - entity will be removed after update loop, if false - entity is removed immediately.\n   * @since 4.3.0 - Added `safe` option.\n   *  The \"safe\" flag will be removed in the next major version release, and the default behavior will be changed to \"safe\".\n   * @see onEntityRemoved\n   */\n  public removeEntity(entity: Entity, safe: boolean = false): Engine {\n    if (!this._entityMap.has(entity.id)) return this;\n    if (!safe) {\n      return this.removeEntityNow(entity);\n    }\n    this._removalRequested.add(entity.id)\n    return this;\n  }\n\n  /**\n   * Gets an entity by its id\n   *\n   * @param {number} id Entity identifier\n   * @return {Entity | undefined} corresponding entity or undefined if it's not found.\n   */\n  public getEntityById(id: number): Entity | undefined {\n    if (this._removalRequested.has(id)) return undefined;\n    return this._entityMap.get(id);\n  }\n\n  /**\n   * Removes a system from engine\n   * Avoid remove the system during update cycle, do it only if your sure what you are doing.\n   * Note: {@link IterativeSystem} has aware guard during update loop, if system removed - updating is being stopped.\n   *\n   * @param system System to remove\n   */\n  public removeSystem(system: System): Engine {\n    const index = this._systems.indexOf(system);\n    if (index === -1) return this;\n    this._systems.splice(index, 1);\n    system.onRemovedFromEngine();\n    system.setEngine(undefined);\n    return this;\n  }\n\n  /**\n   * Updates the engine. This cause updating all the systems in the engine in the order of priority they've been added.\n   *\n   * @param dt Delta time in seconds\n   */\n  public update(dt: number): void {\n    for (const system of this._systems) {\n      system.update(dt);\n      if (system.isRemovalRequested) {\n        this.removeSystem(system);\n      }\n    }\n    if (this._removalRequested.size > 0) {\n      for (const id of this._removalRequested) {\n        const entity = this._entityMap.get(id);\n        if (entity) {\n          this.removeEntityNow(entity);\n        }\n      }\n      this._removalRequested.clear();\n    }\n  }\n\n  /**\n   * Gets a system of the specific class\n   *\n   * @param systemClass Class of the system that should be found\n   */\n  public getSystem<T extends System>(systemClass: Class<T>): T | undefined {\n    return this._systems.find(value => value instanceof systemClass) as T;\n  }\n\n  /**\n   * Remove all systems\n   */\n  public removeAllSystems(): void {\n    const systems = this._systems;\n    this._systems = [];\n    for (const system of systems) {\n      system.onRemovedFromEngine();\n    }\n  }\n\n  /**\n   * Remove all queries.\n   * After remove all queries will be cleared.\n   */\n  public removeAllQueries(): void {\n    const queries = this._queries;\n    this._queries = [];\n    for (const query of queries) {\n      this.disconnectQuery(query);\n      query.clear();\n    }\n  }\n\n  /**\n   * Remove all entities.\n   * onEntityRemoved will be fired for every entity.\n   */\n  public removeAllEntities(): void {\n    this.removeAllEntitiesInternal(false);\n  }\n\n  /**\n   * Removes all entities, queries and systems.\n   * All entities will be removed silently, {@link onEntityRemoved} event will not be fired.\n   * Queries will be cleared.\n   */\n  public clear(): void {\n    this.removeAllEntitiesInternal(true);\n    this.removeAllSystems();\n    this.removeAllQueries();\n  }\n\n  private removeEntityNow(entity: Entity): Engine {\n    const index = this._entities.indexOf(entity);\n    this._entities.splice(index, 1);\n    this._entityMap.delete(entity.id);\n    this.onEntityRemoved.emit(entity);\n    this.disconnectEntity(entity);\n\n    return this;\n  }\n\n  /**\n   * Adds a query to engine. It matches all available in engine entities with query.\n   *\n   * When any entity will be added, removed, their components will be modified - this query will be updated,\n   * until not being removed from engine.\n   *\n   * @param query Entity match query\n   */\n  public addQuery(query: Query): Engine {\n    this.connectQuery(query);\n    query.matchEntities(this.entities);\n    this._queries[this._queries.length] = query;\n    return this;\n  }\n\n  /**\n   * Adds a system to engine, and set its priority inside of engine update loop.\n   *\n   * @param system System to add to the engine\n   * @param priority Value indicating the priority of updating system in update loop. Lower priority\n   *  means sooner update.\n   */\n  public addSystem(system: System, priority: number = 0): Engine {\n    system.setPriority(priority);\n    if (this._systems.length === 0) {\n      this._systems[0] = system;\n    } else {\n      const index = this._systems.findIndex(value => value.priority > priority);\n      if (index === -1) {\n        this._systems[this._systems.length] = system;\n      } else {\n        this._systems.splice(index, 0, system);\n      }\n    }\n    system.setEngine(this);\n    system.onAddedToEngine();\n\n    return this;\n  }\n\n  /**\n   * Removes a query and clear it.\n   *\n   * @param query Entity match query\n   */\n  public removeQuery(query: Query) {\n    const index = this._queries.indexOf(query);\n    if (index == -1) return undefined;\n    this._queries.splice(index, 1);\n    this.disconnectQuery(query);\n    query.clear();\n    return this;\n  }\n\n  /**\n   * Subscribe to any message of the {@link messageType}.\n   * Those messages can be dispatched from any system attached to the engine\n   *\n   * @param {Class<T> | T} messageType - Message type (can be class or any instance, for example string or number)\n   * @param {(value: T) => void} handler - Handler for the message\n   */\n  public subscribe<T>(messageType: Class<T> | T, handler: (value: T) => void): Subscription<T> {\n    return this.addSubscription(messageType, handler);\n  }\n\n  /**\n   * Unsubscribe from messages of specific type\n   *\n   * @param {Class<T>} messageType - Message type\n   * @param {(value: T) => void} handler - Specific handler that must be unsubscribed, if not defined then all handlers\n   *  related to this message type will be unsubscribed.\n   */\n  public unsubscribe<T>(messageType: Class<T> | T, handler?: (value: T) => void): void {\n    this.removeSubscription(messageType, handler);\n  }\n\n  /**\n   * Unsubscribe from all type of messages\n   */\n  public unsubscribeAll(): void {\n    this._subscriptions.length = 0;\n  }\n\n  /**\n   * @internal\n   */\n  public addSubscription<T>(messageType: Class<T> | T, handler: (value: T) => void): Subscription<T> {\n    for (const subscription of this._subscriptions) {\n      if (subscription.equals(messageType, handler)) return subscription;\n    }\n    const subscription = new Subscription<T>(messageType, handler);\n    this._subscriptions.push(subscription);\n    return subscription;\n  }\n\n  /**\n   * @internal\n   */\n  public removeSubscription<T>(messageType: Class<T> | T, handler: ((value: T) => void) | undefined): void {\n    let i = this._subscriptions.length;\n    while (--i >= 0) {\n      const subscription = this._subscriptions[i];\n      if (subscription.equals(messageType, handler)) {\n        this._subscriptions.splice(i, 1);\n        if (handler !== undefined) return;\n      }\n    }\n  }\n\n  /**\n   * @internal\n   */\n  public dispatch<T>(message: T) {\n    for (const subscription of this._subscriptions) {\n      if ((typeof subscription.messageType === 'function' && message instanceof subscription.messageType) || message === subscription.messageType) {\n        subscription.handler(message);\n      }\n    }\n  }\n\n  private connectEntity(entity: Entity) {\n    entity.onComponentAdded.connect(this.onComponentAdded, Number.POSITIVE_INFINITY);\n    entity.onComponentRemoved.connect(this.onComponentRemoved, Number.POSITIVE_INFINITY);\n    entity.onInvalidationRequested.connect(this.onInvalidationRequested, Number.NEGATIVE_INFINITY);\n  }\n\n  private disconnectEntity(entity: Entity) {\n    entity.onComponentAdded.disconnect(this.onComponentAdded);\n    entity.onComponentRemoved.disconnect(this.onComponentRemoved);\n    entity.onInvalidationRequested.disconnect(this.onInvalidationRequested);\n  }\n\n  private connectQuery(query: Query) {\n    this.onEntityAdded.connect(query.entityAdded);\n    this.onEntityRemoved.connect(query.entityRemoved);\n  }\n\n  private disconnectQuery(query: Query) {\n    this.onEntityAdded.disconnect(query.entityAdded);\n    this.onEntityRemoved.disconnect(query.entityRemoved);\n  }\n\n  private removeAllEntitiesInternal(silently: boolean): void {\n    const entities = this._entities;\n    this._entities = [];\n    this._entityMap.clear();\n    for (const entity of entities) {\n      if (!silently) {\n        this.onEntityRemoved.emit(entity);\n      }\n      this.disconnectEntity(entity);\n    }\n  }\n\n  private onComponentAdded = <T>(entity: Entity, component: NonNullable<T>, componentClass?: Class<NonNullable<T>>) => {\n    this._queries.forEach(value => value.entityComponentAdded(entity, component, componentClass));\n  };\n\n  private onInvalidationRequested = (entity: Entity) => {\n    this._queries.forEach(value => value.validateEntity(entity));\n  };\n\n  private onComponentRemoved = <T>(entity: Entity, component: NonNullable<T>, componentClass?: Class<NonNullable<T>>) => {\n    this._queries.forEach(value => value.entityComponentRemoved(entity, component, componentClass));\n  };\n}\n"
  },
  {
    "path": "src/ecs/Entity.ts",
    "content": "import {getComponentClass, getComponentId} from './ComponentId';\nimport {Class} from '../utils/Class';\nimport {Signal} from '../utils/Signal';\nimport {isTag, Tag} from './Tag';\nimport {ILinkedComponent, isLinkedComponent} from './LinkedComponent';\nimport {LinkedComponentList} from './LinkedComponentList';\n\n/**\n * Entity readonly interface\n */\nexport interface ReadonlyEntity {\n  /**\n   * The signal dispatches if new component or tag was added to the entity\n   */\n  readonly onComponentAdded: Signal<ComponentUpdateHandler>;\n  /**\n   * The signal dispatches if component was removed from the entity\n   */\n  readonly onComponentRemoved: Signal<ComponentUpdateHandler>;\n  /**\n   * Returns components map, where key is component identifier, and value is a component itself\n   * @see {@link getComponentId}, {@link Entity.getComponents}\n   */\n  readonly components: Readonly<Record<number, unknown>>;\n  /**\n   * Returns set of tags applied to the entity\n   * @see getComponentId\n   */\n  readonly tags: ReadonlySet<Tag>;\n\n  /**\n   * Returns value indicating whether entity has a specific component or tag\n   *\n   * @param {Class | Tag} componentClassOrTag\n   * @param id Identifier of the LinkedComponent\n   * @example\n   * ```ts\n   * const BERSERK = 10091;\n   * if (!entity.has(Immobile) || entity.has(BERSERK)) {\n   *   const position = entity.get(Position)!;\n   *   position.x += 1;\n   * }\n   * ```\n   */\n  has<T>(componentClassOrTag: Class<T> | Tag, id?: string): boolean;\n\n  /**\n   * Returns value indicating whether entity contains a component instance.\n   * If the component is an instance of ILinkedComponent then all components of its type will be checked for equality.\n   *\n   * @param {T} component\n   * @param {Class<K>} resolveClass\n   * @example\n   * ```ts\n   * const boon = new Boon(BoonType.HEAL);\n   * entity\n   *   .append(new Boon(BoonType.PROTECTION));\n   *   .append(boon);\n   *\n   * if (entity.contains(boon)) {\n   *   logger.info('Ah, sweet. We have not only protection but heal as well!');\n   * }\n   * ```\n   */\n  contains<T extends K, K>(component: T, resolveClass?: Class<K>): boolean;\n\n  /**\n   * Returns value indicating whether entity has a specific component\n   *\n   * @param component\n   * @param id Identifier of the LinkedComponent\n   * @example\n   * ```\n   * if (!entity.hasComponent(Immobile)) {\n   *   const position = entity.get(Position)!;\n   *   position.x += 1;\n   * }\n   * ```\n   */\n  hasComponent<T>(component: Class<T>, id?: string): boolean;\n\n  /**\n   * Returns value indicating whether entity has a specific tag\n   *\n   * @param tag\n   * @example\n   * ```ts\n   * const BERSERK = \"berserk\";\n   * let damage = initialDamage;\n   * if (entity.hasTag(BERSERK)) {\n   *   damage *= 1.2;\n   * }\n   * ```\n   */\n  hasTag(tag: Tag): boolean;\n\n  /**\n   * Returns value indicating whether entity have any of specified components/tags\n   *\n   * @param {Class<unknown> | Tag} componentClassOrTag\n   * @returns {boolean}\n   * @example\n   * ```ts\n   * const IMMORTAL = \"immortal\";\n   * if (!entity.hasAny(Destroy, Destroying, IMMORTAL)) {\n   *   entity.add(new Destroy());\n   * }\n   * ```\n   */\n  hasAny(...componentClassOrTag: Array<Class<unknown> | Tag>): boolean;\n\n  /**\n   * Returns value indicating whether entity have all of specified components/tags\n   *\n   * @param {Class<unknown> | Tag} componentClassOrTag\n   * @returns {boolean}\n   * @example\n   * ```ts\n   * const I_LOVE_GRAVITY = \"no-i-don't\";\n   * if (entity.hasAll(Position, Acceleration, I_LOVE_GRAVITY)) {\n   *   entity.get(Position)!.y += entity.get(Acceleration)!.y * dt;\n   * }\n   * ```\n   */\n  hasAll(...componentClassOrTag: Array<Class<unknown> | Tag>): boolean;\n\n  /**\n   * Returns an array of entity components\n   *\n   * @returns {unknown[]}\n   */\n  getComponents(): unknown[];\n\n  /**\n   * Returns an array of tags applied to the entity\n   */\n  getTags(): Tag[];\n\n  /**\n   * Gets a component instance if it's exists in the entity, otherwise returns `undefined`\n   * - If you want to check presence of the tag then use {@link has} instead.\n   *\n   * @param componentClass Specific component class\n   * @param id Identifier of the LinkedComponent\n   */\n  get<T>(componentClass: Class<T>, id?: string): T | undefined;\n\n  /**\n   * Iterates over instances of linked component appended to the Entity and performs the action over each.<br>\n   * Works and for standard components (action will be called for a single instance in this case).\n   *\n   * @param {Class<T>} componentClass Component`s class\n   * @param {(component: T) => void} action Action to perform over every component instance.\n   * @example\n   * ```ts\n   * class Boon extends LinkedComponent {\n   *   public constructor(\n   *     public type: BoonType,\n   *     public duration: number\n   *   ) { super(); }\n   * }\n   * const entity = new Entity()\n   *   .append(new Boon(BoonType.HEAL, 2))\n   *   .append(new Boon(BoonType.PROTECTION, 3);\n   *\n   * // Let's decrease every boon duration and remove them if they are expired.\n   * entity.iterate(Boon, (boon) => {\n   *   if (--boon.duration <= 0) {\n   *      entity.pick(boon);\n   *   }\n   * });\n   * ```\n   */\n  iterate<T>(componentClass: Class<T>, action: (component: T) => void): void;\n\n  /**\n   * Returns generator with all instances of specified linked component class\n   *\n   * @param {Class<T>} componentClass Component`s class\n   * @example\n   * ```ts\n   * for (const damage of entity.linkedComponents(Damage)) {\n   *   if (damage.value < 0) {\n   *   throw new Error('Damage value can't be less than zero');\n   * }\n   * ```\n   */\n  getAll<T>(componentClass: Class<T>): Generator<T, void, T>;\n\n  /**\n   * Searches a component instance of specified linked component class.\n   * Works and for standard components (predicate will be called for a single instance in this case).\n   *\n   * @param {Class<T>} componentClass\n   * @param {(component: T) => boolean} predicate\n   * @return {T | undefined}\n   */\n  find<T>(componentClass: Class<T>, predicate: (component: T) => boolean): T | undefined;\n\n  /**\n   * Returns number of components of specified class.\n   *\n   * @param {Class<T>} componentClass\n   * @return {number}\n   */\n  lengthOf<T>(componentClass: Class<T>): number;\n}\n\n/**\n * Entity is a general purpose object, which can be marked with tags and can contain different components.\n * So it is just a container, that can represent any in-game entity, like enemy, bomb, configuration, game state, etc.\n *\n * @example\n * ```ts\n * // Here we can see structure of the component \"Position\", it's just a data that can be attached to the Entity\n * // There is no limits for component`s structure.\n * // Components mustn't hold the reference to the entity that it attached to.\n *\n * class Position {\n *   public x:number;\n *   public y:number;\n *\n *   public constructor(x:number = 0, y:number = 0) {\n *     this.x = x;\n *     this.y = y;\n *   }\n * }\n *\n * // We can mark an entity with the tag OBSTACLE. Tag can be represented as a number or string.\n * const OBSTACLE = 10100;\n *\n * const entity = new Entity()\n *  .add(OBSTACLE)\n *  .add(new Position(10, 5));\n * ```\n */\nexport class Entity implements ReadonlyEntity {\n  /**\n   * The signal dispatches if new component or tag was added to the entity. Works for every linked component as well.\n   */\n  public readonly onComponentAdded: Signal<ComponentUpdateHandler> = new Signal();\n  /**\n   * The signal dispatches if component was removed from the entity. Works for every linked component as well.\n   */\n  public readonly onComponentRemoved: Signal<ComponentUpdateHandler> = new Signal();\n  /**\n   * The signal dispatches that invalidation requested for this entity.\n   * Which means that if the entity attached to the engine — its queries will be updated.\n   *\n   * Use {@link Entity.invalidate} method in case if in query test function is using component properties or complex\n   * logic.\n   *\n   * Only adding/removing components and tags are tracked by Engine. So you need to request queries invalidation\n   * manually, if some of your queries depends on logic or component`s properties.\n   */\n  public readonly onInvalidationRequested: Signal<(entity: Entity) => void> = new Signal();\n\n  /**\n   * Unique id identifier\n   */\n  public readonly id = entityId++;\n\n  private _components: Record<number, unknown> = {};\n  private _linkedComponents: Record<number, LinkedComponentList<ILinkedComponent>> = {};\n  private _tags: Set<Tag> = new Set();\n\n  /**\n   * Returns components map, where key is component identifier, and value is a component itself\n   * @see {@link getComponentId}, {@link Entity.getComponents}\n   */\n  public get components(): Readonly<Record<number, unknown>> {\n    return this._components;\n  }\n\n  /**\n   * Returns set of tags applied to the entity\n   * @see getComponentId\n   */\n  public get tags(): ReadonlySet<Tag> {\n    return this._tags;\n  }\n\n  /**\n   * Adds a component or tag to the entity.\n   * It's a unified shorthand for {@link addComponent} and {@link addTag}.\n   *\n   * - If a component of the same type already exists in entity, it will be replaced by the passed one (only if\n   *  component itself is not the same, in this case - no actions will be done).\n   * - If the tag is already present in the entity - no actions will be done.\n   * - During components replacement {@link onComponentRemoved} and {@link onComponentAdded} are will be triggered\n   *  sequentially.\n   * - If there is no component of the same type, or the tag is not present in the entity - then only\n   * - If the passed component is an instance of ILinkedComponent then all existing instances will be removed, and the\n   *  passed instance will be added to the Entity. {@link onComponentRemoved} will be triggered for every removed\n   *  instance and {@link onComponentAdded} will be triggered for the passed component.\n   * - Linked component always replaces all existing instances. Even if the passed instance already exists in the\n   *  Entity - all existing linked components will be removed anyway, and replaced with the passed one.\n   *\n   * @throws Throws error if component is null or undefined, or if component is not an instance of the class as well\n   * @param {T | Tag} componentOrTag Component instance or Tag\n   * @param {K} resolveClass Class that should be used as resolving class.\n   *  Passed class always should be an ancestor of Component's class.\n   *  It has sense only if component instance is passed, but not the Tag.\n   * @returns {Entity} Reference to the entity itself. It helps to build chain of calls.\n   * @see {@link addComponent, appendComponent}, {@link addTag}\n   * @example\n   * ```ts\n   * const BULLET = 1;\n   * const EXPLOSIVE = \"explosive\";\n   * const entity = new Entity()\n   *  .add(new Position())\n   *  .add(new View())\n   *  .add(new Velocity())\n   *  .add(BULLET)\n   *  .add(EXPLOSIVE);\n   * ```\n   */\n  public add<T extends K, K extends unknown>(componentOrTag: NonNullable<T> | Tag, resolveClass?: Class<K>): Entity {\n    if (isTag(componentOrTag)) {\n      this.addTag(componentOrTag);\n    } else {\n      this.addComponent(componentOrTag, resolveClass);\n    }\n    return this;\n  }\n\n  /**\n   * Appends a linked component to the entity.\n   *\n   * - If linked component is not exists, then it will be added to the Entity and {@link onComponentAdded}\n   * will be triggered.\n   * - If component already exists in the entity, then passed one will be appended to the tail. {@link onComponentAdded}\n   *  will be triggered as well.\n   *\n   * It's a shorthand to {@link appendComponent}\n   *\n   * @throws Throws error if component is null or undefined, or if component is not an instance of the class as well\n   * @param {T | Tag} component ILinkedComponent instance\n   * @param {K} resolveClass Class that should be used as resolving class.\n   *  Passed class always should be an ancestor of Component's class.\n   *\n   * @returns {Entity} Reference to the entity itself. It helps to build chain of calls.\n   * @see {@link addComponent}\n   * @see {@link appendComponent}\n   * @example\n   * ```ts\n   * const damage = new Damage();\n   * const entity = new Entity()\n   *  .append(new Damage(1))\n   *  .append(new Damage(2))\n   *\n   *  const damage = entity.get(Damage);\n   *  while (entity.has(Damage)) {\n   *    const entity = entity.withdraw(Damage);\n   *    print(damage.value);\n   *  }\n   * ```\n   */\n  public append<T extends K, K extends ILinkedComponent>(component: NonNullable<T>, resolveClass?: Class<K>): Entity {\n    return this.appendComponent(component, resolveClass);\n  }\n\n  /**\n   * Removes first appended linked component instance of the specified type.\n   * Unlike {@link remove} and {@link removeComponent} remaining linked components stays in the Entity.\n   *\n   * - If linked component exists in the Entity, then it will be removed from Entity and {@link onComponentRemoved}\n   * will be triggered.\n   *\n   * @param {Class<T>} componentClass\n   * @return {T | undefined} Component instance if any of the specified type exists in the entity, otherwise undefined\n   * @example\n   * ```ts\n   * const entity = new Entity()\n   *   .append(new Damage(1))\n   *   .append(new Damage(2))\n   *   .append(new Damage(3));\n   *\n   * entity.withdraw(Damage);\n   * entity.iterate(Damage, (damage) => {\n   *   print('Remaining damage: ' + damage.value);\n   * });\n   *\n   * // Remaining damage: 2\n   * // Remaining damage: 3\n   * ```\n   */\n  public withdraw<T>(componentClass: Class<T>): T | undefined {\n    const component = this.get(componentClass);\n    if (component === undefined) return;\n    if (isLinkedComponent(component)) {\n      return this.withdrawComponent(component, componentClass as Class<ILinkedComponent>);\n    } else {\n      return this.remove(componentClass);\n    }\n  }\n\n  /**\n   * Removes particular linked component instance from the Entity by its id.\n   *\n   * - If linked component instance exists in the Entity, then it will be removed from Entity and\n   * {@link onComponentRemoved} will be triggered.\n   *\n   * @param {Class<K>} resolveClass Resolve class\n   * @param {string} id Linked component id\n   * @return {T | undefined} Component instance if it exists in the entity, otherwise undefined\n   */\n  public pick<T extends ILinkedComponent>(resolveClass: Class<T>, id: string): T | undefined;\n  /**\n   * Removes particular linked component instance from the Entity.\n   *\n   * - If linked component instance exists in the Entity, then it will be removed from Entity and\n   * {@link onComponentRemoved} will be triggered.\n   *\n   * @param {NonNullable<T>} component Linked component instance\n   * @param {Class<K> | undefined} resolveClass Resolve class\n   * @return {T | undefined} Component instance if it exists in the entity, otherwise undefined\n   */\n  public pick<T>(component: NonNullable<T>, resolveClass?: Class<T>): T | undefined;\n  public pick<T>(componentOrResolveClass: NonNullable<T> | Class<T>, resolveClassOrId?: Class<T> | string): T | undefined {\n    if (typeof resolveClassOrId === 'string') {\n      const component = this.find<T>(componentOrResolveClass as Class<T>, (component) => isLinkedComponent(component) && component.id === resolveClassOrId);\n      if (isLinkedComponent(component)) {\n        return this.withdrawComponent(component, componentOrResolveClass as Class<ILinkedComponent>);\n      }\n      return undefined;\n    }\n    if (isLinkedComponent(componentOrResolveClass)) {\n      return this.withdrawComponent(componentOrResolveClass, resolveClassOrId as Class<ILinkedComponent>);\n    }\n    return this.remove(resolveClassOrId ?? getComponentClass(componentOrResolveClass as NonNullable<T>));\n  }\n\n  /**\n   * Adds a component to the entity.\n   *\n   * - If a component of the same type already exists in entity, it will be replaced by the passed one (only if\n   *  component itself is not the same, in this case - no actions will be done).\n   * - During components replacement {@link onComponentRemoved} and {@link onComponentAdded} are will be triggered\n   *  sequentially.\n   * - If there is no component of the same type - then only {@link onComponentAdded} will be triggered.\n   *\n   * @throws Throws error if component is null or undefined, or if component is not an instance of the class as well\n   * @param {T} component Component instance\n   * @param {K} resolveClass Class that should be used as resolving class.\n   *  Passed class always should be an ancestor of Component's class.\n   * @returns {Entity} Reference to the entity itself. It helps to build chain of calls.\n   * @see {@link add}, {@link addTag}\n   * @example\n   * ```ts\n   * const BULLET = 1;\n   * const entity = new Entity()\n   *  .addComponent(new Position())\n   *  .addComponent(new View())\n   *  .add(BULLET);\n   * ```\n   */\n  public addComponent<T extends K, K extends unknown>(component: NonNullable<T>, resolveClass?: Class<K>): Entity {\n    const componentClass = getComponentClass(component, resolveClass);\n    const id = getComponentId(componentClass, true)!;\n    const linkedComponent = isLinkedComponent(component);\n    if (this._components[id] !== undefined) {\n      if (!linkedComponent && component === this._components[id]) {\n        return this;\n      }\n      this.remove(componentClass);\n    }\n    if (linkedComponent) {\n      this.append(component as ILinkedComponent, resolveClass as Class<ILinkedComponent>);\n    } else {\n      this._components[id] = component;\n      this.dispatchOnComponentAdded(component);\n    }\n    return this;\n  }\n\n  /**\n   * Appends a linked component to the entity.\n   *\n   * - If linked component is not exists, then it will be added via `addComponent` method and {@link onComponentAdded}\n   * will be triggered.\n   * - If component already exists in the entity, then passed one will be appended to the tail. {@link\n    * onComponentAdded} won't be triggered.\n   *\n   * @throws Throws error if component is null or undefined, or if component is not an instance of the class as well\n   * @param {T | Tag} component ILinkedComponent instance\n   * @param {K} resolveClass Class that should be used as resolving class.\n   *  Passed class always should be an ancestor of Component's class.\n   *\n   * @returns {Entity} Reference to the entity itself. It helps to build chain of calls.\n   * @see {@link append}\n   * @see {@link addComponent}\n   * @example\n   * ```ts\n   * const damage = new Damage();\n   * const entity = new Entity()\n   *  .append(new Damage())\n   *  .append(new Damage())\n   *\n   *  const damage = entity.get(Damage);\n   *  while (damage !== undefined) {\n   *    print(damage.value);\n   *    damage = damage.next;\n   *  }\n   * ```\n   */\n  public appendComponent<T extends K, K extends ILinkedComponent>(component: NonNullable<T>, resolveClass?: Class<K>): Entity {\n    const componentClass = getComponentClass(component, resolveClass);\n    const componentId = getComponentId(componentClass, true)!;\n    const componentList = this.getLinkedComponentList(componentId)!;\n    componentList.add(component);\n    if (this._components[componentId] === undefined) {\n      this._components[componentId] = componentList.head;\n    }\n    this.dispatchOnComponentAdded(component);\n    return this;\n  }\n\n  /**\n   * Adds a tag to the entity.\n   *\n   * - If the tag is already present in the entity - no actions will be done.\n   * - If there is such tag in the entity then {@link onComponentAdded} will be triggered.\n   *\n   * @param {Tag} tag Tag\n   * @returns {Entity} Reference to the entity itself. It helps to build chain of calls.\n   * @see {@link add}, {@link addComponent}\n   * @example\n   * ```ts\n   * const DEVELOPER = \"developer;\n   * const EXHAUSTED = 2;\n   * const  = \"game-over\";\n   * const entity = new Entity()\n   *  .addTag(DEVELOPER)\n   *  .add(EXHAUSTED)\n   * ```\n   */\n  public addTag(tag: Tag): Entity {\n    if (!this._tags.has(tag)) {\n      this._tags.add(tag);\n      this.dispatchOnComponentAdded(tag);\n    }\n    return this;\n  }\n\n  /**\n   * Returns componentClassOrTag indicating whether entity has a specific component or tag\n   *\n   * @param componentClassOrTag\n   * @param id Identifier of the LinkedComponent\n   * @example\n   * ```ts\n   * const BERSERK = 10091;\n   * if (!entity.has(Immobile) || entity.has(BERSERK)) {\n   *   const position = entity.get(Position)!;\n   *   position.x += 1;\n   * }\n   * ```\n   */\n  public has<T>(componentClassOrTag: Class<T> | Tag, id?: string): boolean {\n    if (isTag(componentClassOrTag)) {\n      return this.hasTag(componentClassOrTag);\n    }\n    return this.hasComponent(componentClassOrTag, id);\n  }\n\n  /**\n   * Returns value indicating whether entity contains a component instance.\n   * If the component is an instance of ILinkedComponent then all components of its type will be checked for equality.\n   *\n   * @param {NonNullable<T>} component\n   * @param {Class<K>} resolveClass\n   * @example\n   * ```ts\n   * const boon = new Boon(BoonType.HEAL);\n   * entity\n   *   .append(new Boon(BoonType.PROTECTION));\n   *   .append(boon);\n   *\n   * if (entity.contains(boon)) {\n   *   logger.info('Ah, sweet. We have not only protection but heal as well!');\n   * }\n   * ```\n   */\n  public contains<T extends K, K>(component: NonNullable<T>, resolveClass?: Class<K>): boolean {\n    const componentClass = getComponentClass(component, resolveClass);\n    if (isLinkedComponent(component)) {\n      return this.find(componentClass, (value) => value === component) !== undefined;\n    }\n    return this.get(componentClass) === component;\n  }\n\n  /**\n   * Returns value indicating whether entity has a specific component\n   *\n   * @param component Component class\n   * @param id Identifier of the LinkedComponent\n   *\n   * @example\n   * ```\n   * if (!entity.hasComponent(Immobile)) {\n   *   const position = entity.get(Position)!;\n   *   position.x += 1;\n   * }\n   * ```\n   */\n  public hasComponent<T>(component: Class<T>, id?: string): boolean {\n    return this.get(component, id) !== undefined;\n  }\n\n  /**\n   * Returns value indicating whether entity has a specific tag\n   *\n   * @param tag\n   * @example\n   * ```ts\n   * const BERSERK = \"berserk\";\n   * let damage = initialDamage;\n   * if (entity.hasTag(BERSERK)) {\n   *   damage *= 1.2;\n   * }\n   * ```\n   */\n  public hasTag(tag: Tag): boolean {\n    return this._tags.has(tag);\n  }\n\n  /**\n   * Returns value indicating whether entity have any of specified components/tags\n   *\n   * @param {Class<unknown> | Tag} componentClassOrTag\n   * @returns {boolean}\n   * @example\n   * ```ts\n   * const IMMORTAL = \"immortal\";\n   * if (!entity.hasAny(Destroy, Destroying, IMMORTAL)) {\n   *   entity.add(new Destroy());\n   * }\n   * ```\n   */\n  public hasAny(...componentClassOrTag: Array<Class<unknown> | Tag>): boolean {\n    return componentClassOrTag.some(value => this.has(value));\n  }\n\n  /**\n   * Returns value indicating whether entity have all of specified components/tags\n   *\n   * @param {Class<unknown> | Tag} componentClassOrTag\n   * @returns {boolean}\n   * @example\n   * ```ts\n   * const I_LOVE_GRAVITY = \"no-i-don't\";\n   * if (entity.hasAll(Position, Acceleration, I_LOVE_GRAVITY)) {\n   *   entity.get(Position)!.y += entity.get(Acceleration)!.y * dt;\n   * }\n   * ```\n   */\n  public hasAll(...componentClassOrTag: Array<Class<unknown> | Tag>): boolean {\n    return componentClassOrTag.every(value => this.has(value));\n  }\n\n  /**\n   * Gets a component instance if it's exists in the entity, otherwise returns `undefined`\n   * - If you want to check presence of the tag then use {@link has} instead.\n   *\n   * @param componentClass Specific component class\n   * @param id Identifier of the LinkedComponent\n   */\n  public get<T>(componentClass: Class<T>, id?: string): T | undefined {\n    const cid = getComponentId(componentClass);\n    if (cid === undefined) return undefined;\n    let component = this._components[cid];\n    if (id !== undefined) {\n      if (isLinkedComponent(component)) {\n        while (component !== undefined) {\n          if ((component as ILinkedComponent).id === id) return component as T;\n          component = (component as ILinkedComponent).next;\n        }\n      }\n      return undefined;\n    }\n    return this._components[cid] as T;\n  }\n\n  /**\n   * Returns an array of entity components\n   *\n   * @returns {unknown[]}\n   */\n  public getComponents(): unknown[] {\n    return Array.from(Object.values(this._components));\n  }\n\n  /**\n   * Returns an array of tags applied to the entity\n   */\n  public getTags(): Tag[] {\n    return Array.from(this._tags);\n  }\n\n  /**\n   * Removes a component or tag from the entity.\n   *  In case if the component or tag is present - then {@link onComponentRemoved} will be\n   *  dispatched after removing it from the entity.\n   *\n   * If linked component type provided:\n   * - For each instance of linked component {@link onComponentRemoved} will be called\n   * - Only head of the linked list will be returned.\n   *\n   * If you need to get all instances use {@link withdraw} or {@link pick} instead, or check {@link iterate},\n   * {@link getAll}\n   *\n   * It's a shorthand for {@link removeComponent}\n   *\n   * @param componentClassOrTag Specific component class or tag\n   * @returns Component instance or `undefined` if it doesn't exists in the entity, or tag was removed\n   * @see {@link withdraw}\n   * @see {@link pick}\n   */\n  public remove<T>(componentClassOrTag: Class<T> | Tag): T | undefined {\n    if (isTag(componentClassOrTag)) {\n      this.removeTag(componentClassOrTag);\n      return undefined;\n    }\n    return this.removeComponent(componentClassOrTag);\n  }\n\n  /**\n   * Removes a component from the entity.\n   *  In case if the component or tag is present - then {@link onComponentRemoved} will be\n   *  dispatched after removing it from the entity.\n   *\n   * If linked component type provided:\n   * - For each instance of linked component {@link onComponentRemoved} will be called\n   * - Only head of the linked list will be returned.\n   *\n   * If you need to get all instances use {@link withdraw} or {@link pick} instead, or check {@link iterate},\n   * {@link getAll}\n   *\n   * @param componentClassOrTag Specific component class\n   * @returns Component instance or `undefined` if it doesn't exists in the entity\n   */\n  public removeComponent<T>(componentClassOrTag: Class<T>): T | undefined {\n    const id = getComponentId(componentClassOrTag);\n    if (id === undefined || this._components[id] === undefined) {\n      return undefined;\n    }\n\n    let value = this._components[id]!;\n    if (isLinkedComponent(value)) {\n      const list = this.getLinkedComponentList(componentClassOrTag)!;\n      while (!list.isEmpty) {\n        this.withdraw(componentClassOrTag);\n      }\n    } else {\n      delete this._components[id];\n      this.dispatchOnComponentRemoved(value);\n    }\n\n    return value as T;\n  }\n\n  /**\n   * Removes a tag from the entity.\n   *  In case if the component tag is present - then {@link onComponentRemoved} will be\n   *  dispatched after removing it from the entity\n   *\n   * @param {Tag} tag Specific tag\n   * @returns {void}\n   */\n  public removeTag(tag: Tag): void {\n    if (this._tags.has(tag)) {\n      this._tags.delete(tag);\n      this.dispatchOnComponentRemoved(tag);\n    }\n  }\n\n  /**\n   * Removes all components and tags from entity\n   */\n  public clear(): void {\n    this._components = {};\n    this._linkedComponents = {};\n    this._tags.clear();\n  }\n\n  /**\n   * Copies content from entity to itself.\n   * Linked components structure will be copied by the link, because we can't duplicate linked list order without\n   * cloning components itself. So modifying linked components in the copy will affect linked components in copy\n   * source.\n   *\n   * @param {Entity} entity\n   * @return {this}\n   */\n  public copyFrom(entity: Entity): this {\n    this._components = Object.assign({}, entity._components);\n    this._linkedComponents = Object.assign({}, entity._linkedComponents);\n    this._tags = new Set(entity._tags);\n    return this;\n  }\n\n  /**\n   * Iterates over instances of linked component appended to the Entity and performs the action over each.<br>\n   * Works and for standard components (action will be called for a single instance in this case).\n   *\n   * @param {Class<T>} componentClass Component`s class\n   * @param {(component: T) => void} action Action to perform over every component instance.\n   * @example\n   * ```ts\n   * class Boon extends LinkedComponent {\n   *   public constructor(\n   *     public type: BoonType,\n   *     public duration: number\n   *   ) { super(); }\n   * }\n   * const entity = new Entity()\n   *   .append(new Boon(BoonType.HEAL, 2))\n   *   .append(new Boon(BoonType.PROTECTION, 3);\n   *\n   * // Let's decrease every boon duration and remove them if they are expired.\n   * entity.iterate(Boon, (boon) => {\n   *   if (--boon.duration <= 0) {\n   *      entity.pick(boon);\n   *   }\n   * });\n   * ```\n   */\n  public iterate<T>(componentClass: Class<T>, action: (component: T) => void): void {\n    if (!this.hasComponent(componentClass)) return;\n    this.getLinkedComponentList(componentClass)?.iterate(action);\n  }\n\n  /**\n   * Returns generator with all instances of specified linked component class\n   *\n   * @param {Class<T>} componentClass Component`s class\n   * @example\n   * ```ts\n   * for (const damage of entity.linkedComponents(Damage)) {\n   *   if (damage.value < 0) {\n   *   throw new Error('Damage value can't be less than zero');\n   * }\n   * ```\n   */\n  public* getAll<T>(componentClass: Class<T>): Generator<T, void, T | undefined> {\n    if (!this.hasComponent(componentClass)) return;\n    const list = this.getLinkedComponentList(componentClass, false);\n    if (list === undefined) return undefined;\n    yield* list.nodes();\n  }\n\n  /**\n   * Searches a component instance of specified linked component class.\n   * Works and for standard components (predicate will be called for a single instance in this case).\n   *\n   * @param {Class<T>} componentClass\n   * @param {(component: T) => boolean} predicate\n   * @return {T | undefined}\n   */\n  public find<T>(componentClass: Class<T>, predicate: (component: T) => boolean): T | undefined {\n    const componentIdToFind = getComponentId(componentClass, false);\n    if (componentIdToFind === undefined) return undefined;\n    const component = this._components[componentIdToFind];\n    if (component === undefined) return undefined;\n    if (isLinkedComponent(component)) {\n      let linkedComponent: ILinkedComponent | undefined = component;\n      while (linkedComponent !== undefined) {\n        if (predicate(linkedComponent as T)) return linkedComponent as T;\n        linkedComponent = linkedComponent.next;\n      }\n    } else return predicate(component as T) ? component as T : undefined;\n  }\n\n  /**\n   * Returns number of components of specified class.\n   *\n   * @param {Class<T>} componentClass\n   * @return {number}\n   */\n  public lengthOf<T>(componentClass: Class<T>): number {\n    let result = 0;\n    this.iterate(componentClass, () => {\n      result++;\n    });\n    return result;\n  }\n\n  /**\n   * Use this method to dispatch that entity component properties were changed, in case if\n   * queries predicates are depends on them.\n   * Components properties are not tracking by Engine itself, because it's too expensive.\n   */\n  public invalidate(): void {\n    this.onInvalidationRequested.emit(this);\n  }\n\n  /**\n   * @internal\n   * @param {EntitySnapshot} result\n   * @param {T} changedComponentOrTag\n   * @param {Class<T>} resolveClass\n   */\n  public takeSnapshot<T>(result: EntitySnapshot, changedComponentOrTag?: T, resolveClass?: Class<T>): void {\n    const previousState = result.previous as Entity;\n    if (result.current !== this) {\n      result.current = this;\n      previousState.copyFrom(this);\n    }\n\n    if (changedComponentOrTag === undefined) {\n      return;\n    }\n\n    if (isTag(changedComponentOrTag)) {\n      const previousTags = previousState._tags;\n      if (this.has(changedComponentOrTag)) {\n        previousTags.delete(changedComponentOrTag);\n      } else {\n        previousTags.add(changedComponentOrTag);\n      }\n    } else {\n      const componentClass = resolveClass ?? Object.getPrototypeOf(changedComponentOrTag).constructor;\n      const componentId = getComponentId(componentClass!, true)!;\n      const previousComponents = previousState._components;\n      if (this.has(componentClass)) {\n        delete previousComponents[componentId];\n      } else {\n        previousComponents[componentId] = changedComponentOrTag;\n      }\n    }\n  }\n\n  /**\n   * @internal\n   */\n  public getLinkedComponentList(componentClassOrId: number | Class<any>, createIfNotExists = true): LinkedComponentList<any> | undefined {\n    if (typeof componentClassOrId !== 'number') {\n      componentClassOrId = getComponentId(componentClassOrId)!;\n    }\n    if (this._linkedComponents[componentClassOrId] !== undefined || !createIfNotExists) {\n      return this._linkedComponents[componentClassOrId];\n    } else {\n      return this._linkedComponents[componentClassOrId] = new LinkedComponentList<ILinkedComponent>();\n    }\n  }\n\n  private withdrawComponent<T extends K, K extends ILinkedComponent>(component: NonNullable<T>, resolveClass?: Class<K>): T | undefined {\n    const componentClass = getComponentClass(component, resolveClass);\n    const componentList = this.getLinkedComponentList(componentClass, false);\n    if (!this.hasComponent(componentClass) || componentList === undefined) return undefined;\n    const result = componentList.remove(component) ? component : undefined;\n    const componentId = getComponentId(componentClass, true)!;\n    if (componentList.isEmpty) {\n      delete this._components[componentId];\n      delete this._linkedComponents[componentId];\n    } else {\n      this._components[componentId] = componentList.head;\n    }\n    if (result !== undefined) {\n      this.dispatchOnComponentRemoved(result);\n    }\n    return result;\n  }\n\n  private dispatchOnComponentAdded<T>(component: NonNullable<T>): void {\n    if (this.onComponentAdded.hasHandlers) {\n      this.onComponentAdded.emit(this, component);\n    }\n  }\n\n  private dispatchOnComponentRemoved<T>(value: NonNullable<T>): void {\n    if (this.onComponentRemoved.hasHandlers) {\n      this.onComponentRemoved.emit(this, value);\n    }\n  }\n}\n\n/**\n * EntitySnapshot is a content container that displays the difference between the current state of Entity and its\n * previous state.\n *\n * The {@link EntitySnapshot.current} property always reflects the current state, and {@link EntitySnapshot.previous} -\n * previous one. So you can understand which components have been added and which have been removed.\n *\n * <p>It is important to note that changes in the data of the same entity components will not be reflected in the\n * snapshot, even if a manual invalidation of the entity has been triggered.</p>\n */\nexport class EntitySnapshot {\n  private _current?: Entity;\n  private _previous: ReadonlyEntity = new Entity();\n\n  /**\n   * Gets an instance of the actual entity\n   * @returns {Entity}\n   */\n  public get current(): Entity {\n    return this._current!;\n  }\n\n  /**\n   * @internal\n   */\n  public set current(value: Entity) {\n    this._current = value;\n  }\n\n  /**\n   * Gets an instance of the previous state of entity\n   */\n  public get previous(): ReadonlyEntity {\n    return this._previous;\n  }\n}\n\n/**\n * Component update handler type.\n * @see {@link Entity.onComponentAdded}\n * @see {@link Entity.onComponentRemoved}\n */\nexport type ComponentUpdateHandler = <T>(entity: Entity, component: NonNullable<T>, componentClass?: Class<NonNullable<T>>) => void;\n\n/**\n * Entity ids enumerator\n */\nlet entityId: number = 1;"
  },
  {
    "path": "src/ecs/IterativeSystem.ts",
    "content": "import {Query, QueryBuilder, QueryPredicate} from './Query';\nimport {Entity} from './Entity';\nimport {ReactionSystem} from './ReactionSystem';\n\n/**\n * Iterative system made for iterating over entities that matches its query.\n *\n * @example\n * You have a View component, that is responsible for entity displaying and contains an image.\n * So every step you want to update image positions, that can depends on Position component.\n *\n * ```ts\n * class ViewSystem extends IterativeSystem {\n *   constructor(container:Container) {\n *      super(new Query((entity:Entity) => entity.hasAll(View, Position));\n *      this.container = container;\n *   }\n *\n *   // Update entity view position on screen, via position component data\n *   updateEntity(entity:Entity) {\n *     const {view} = entity.get(View)!;\n *     const {x, y) = entity.get(Position)!;\n *     view.x = x;\n *     view.y = y;\n *   }\n *\n *   // Add entity view from screen\n *   entityAdded = ({entity}:EntitySnapshot) => {\n *    this.container.add(entity.get(View)!.view);\n *   }\n *\n *   // Remove entity view from screen\n *   entityRemoved = (snapshot:EntitySnapshot) => {\n *    this.container.remove(snapshot.get(View)!.view);\n *   }\n * }\n * ```\n */\nexport abstract class IterativeSystem extends ReactionSystem {\n  private _removed: boolean = false;\n\n  protected constructor(query: Query | QueryBuilder | QueryPredicate) {\n    super(query);\n  }\n\n  public update(dt: number) {\n    this.updateEntities(dt);\n  }\n\n  public onAddedToEngine() {\n    this._removed = false;\n    super.onAddedToEngine();\n  }\n\n  public onRemovedFromEngine() {\n    this._removed = true;\n    super.onRemovedFromEngine();\n  }\n\n  protected updateEntities(dt: number) {\n    for (let entity of this.query.entities) {\n      if (this._removed) return;\n      this.updateEntity(entity, dt);\n    }\n  }\n\n  /**\n   * Update entity\n   *\n   * @param entity Entity to update\n   * @param dt Delta time in seconds\n   */\n  protected abstract updateEntity(entity: Entity, dt: number): void;\n}\n"
  },
  {
    "path": "src/ecs/LinkedComponent.ts",
    "content": "/**\n * Linked list interface for linked components\n * @see {@link Entity.append}\n */\n\nexport interface ILinkedComponent {\n  id?: string;\n  next?: ILinkedComponent;\n}\n\n/**\n * Simple ILinkedComponent implementation\n * @see {@link Entity.append}\n */\nexport class LinkedComponent implements ILinkedComponent {\n  public next?: this = undefined;\n\n  public constructor(public id?: string) {\n  }\n}\n\n/**\n * @internal\n */\nexport function isLinkedComponent(component: any): component is ILinkedComponent {\n  return component !== undefined && component.hasOwnProperty('next');\n}\n"
  },
  {
    "path": "src/ecs/LinkedComponentList.ts",
    "content": "import {ILinkedComponent} from './LinkedComponent';\n\nexport class LinkedComponentList<T extends ILinkedComponent> {\n  private _head?: T;\n\n  public get head(): T | undefined {\n    return this._head;\n  }\n\n  public get isEmpty(): boolean {\n    return this._head === undefined;\n  }\n\n  public add(linkedComponent: T): void {\n    let prev: T | undefined = undefined;\n    let current: T | undefined = this._head;\n    while (current !== undefined) {\n      if (current === linkedComponent) {\n        throw new Error('Component is already appended, appending it once again will break linked items order');\n      }\n      prev = current;\n      current = current.next as (T | undefined);\n    }\n    if (this._head === undefined) {\n      this._head = linkedComponent;\n    } else {\n      prev!.next = linkedComponent;\n    }\n  }\n\n  public remove(linkedComponent: T): boolean {\n    const [prev, current] = this.find(linkedComponent);\n    if (current === undefined) {\n      return false;\n    }\n    if (prev === undefined) {\n      this._head = current.next as (T | undefined);\n    } else {\n      prev.next = current.next;\n    }\n    return true;\n  }\n\n  public* nodes() {\n    let node = this.head;\n    while (node !== undefined) {\n      yield node;\n      node = node.next as (T | undefined);\n    }\n  }\n\n  public iterate(action: (value: T) => void): void {\n    for (const node of this.nodes()) {\n      action(node);\n    }\n  }\n\n  public clear(): void {\n    this._head = undefined;\n  }\n\n  private find(linkedComponent: T): [prev: T | undefined, current: T | undefined] {\n    let prev: T | undefined;\n    let current: T | undefined = this._head;\n\n    while (current !== undefined) {\n      if (current === linkedComponent) {\n        return [prev, current];\n      }\n      prev = current;\n      current = current.next as (T | undefined);\n    }\n    return [undefined, undefined];\n  }\n}\n"
  },
  {
    "path": "src/ecs/Query.ts",
    "content": "import {getComponentId} from './ComponentId';\nimport {Entity, EntitySnapshot} from './Entity';\nimport {isTag, Tag} from './Tag';\nimport {Signal} from '../utils/Signal';\nimport {Class} from '../utils/Class';\n\n/**\n * Query Predicate is the type that describes a function that compares Entities with the conditions it sets.\n * In other words, it's a function that determines whether Entities meets the right conditions to get into a\n * given Query or not.\n */\nexport type QueryPredicate = (entity: Entity) => boolean;\n\n/**\n * Query represents list of entities that matches query request.\n * @see QueryBuilder\n */\nexport class Query {\n  /**\n   * Signal dispatches if new matched entity were added\n   */\n  public onEntityAdded: Signal<(snapshot: EntitySnapshot) => void> = new Signal();\n  /**\n   * Signal dispatches if entity stops matching query\n   */\n  public onEntityRemoved: Signal<(snapshot: EntitySnapshot) => void> = new Signal();\n\n  private readonly _snapshot: EntitySnapshot = new EntitySnapshot();\n  private readonly _predicate: QueryPredicate;\n  private _entities: Entity[] = [];\n\n  /**\n   * Initializes Query instance\n   * @param predicate Matching predicate\n   */\n  public constructor(predicate: QueryPredicate) {\n    this._predicate = predicate;\n  }\n\n  /**\n   * Entities list which matches the query\n   */\n  public get entities(): ReadonlyArray<Entity> {\n    return this._entities;\n  }\n\n  /**\n   * Returns the first entity in the query or `undefined` if query is empty.\n   * @returns {Entity | undefined}\n   */\n  public get first(): Entity | undefined {\n    if (this._entities.length === 0) return undefined;\n    return this._entities[0];\n  }\n\n  /**\n   * Returns the last entity in the query or `undefined` if query is empty.\n   * @returns {Entity | undefined}\n   */\n  public get last(): Entity | undefined {\n    if (this._entities.length === 0) return undefined;\n    return this._entities[this._entities.length - 1];\n  }\n\n  /**\n   * Returns the number of the entities in the query\n   * @returns {Entity | undefined}\n   */\n  public get length(): number {\n    return this._entities.length;\n  }\n\n  /**\n   * Returns the number of entities that have been tested by the predicate.\n   * @param {(entity: Entity) => boolean} predicate\n   * @returns {number}\n   */\n  public countBy(predicate: QueryPredicate): number {\n    let result = 0;\n    for (const entity of this._entities) {\n      if (predicate(entity)) result++;\n    }\n    return result;\n  }\n\n  /**\n   * Returns the first entity from the query, that was accepted by predicate\n   * @param {(entity: Entity) => boolean} predicate - function that will be called for every entity in the query until\n   *  the result of the function become true.\n   * @returns {Entity | undefined}\n   */\n  public find(predicate: QueryPredicate): Entity | undefined {\n    return this._entities.find(predicate);\n  }\n\n  /**\n   * Returns new array of entities, which passed testing via predicate\n   * @param {(entity: Entity) => boolean} predicate - function that will be called for every entity in the query.\n   *  If function returns `true` - entity will stay in the array, if `false` than it will be removed.\n   * @returns {Entity[]}\n   */\n  public filter(predicate: QueryPredicate): Entity[] {\n    return this._entities.filter(predicate);\n  }\n\n  /**\n   * Returns a value that indicates whether the entity is in the Query.\n   * @param {Entity} entity\n   * @returns {boolean}\n   */\n  public has(entity: Entity): boolean {\n    return this._entities.indexOf(entity) !== -1;\n  }\n\n  /**\n   * This method is matching passed list of entities with predicate of the query to determine\n   * if entities are the part of query or not.\n   *\n   * Entities that will pass testing will become a part of the query\n   */\n  public matchEntities(entities: ReadonlyArray<Entity>) {\n    entities.forEach((entity) => this.entityAdded(entity));\n  }\n\n  /**\n   * Gets a value indicating that query is empty\n   */\n  public get isEmpty(): boolean {\n    return this.entities.length == 0;\n  }\n\n  /**\n   * Clears the list of entities of the query\n   */\n  public clear(): void {\n    this._entities = [];\n  }\n\n  /**\n   * @internal\n   */\n  public validateEntity(entity: Entity): void {\n    const index = this._entities.indexOf(entity);\n    const isMatch = this._predicate(entity);\n    if (index !== -1 && !isMatch) {\n      this.entityRemoved(entity);\n    } else {\n      this.entityAdded(entity);\n    }\n  }\n\n  /**\n   * @internal\n   */\n  public entityAdded = (entity: Entity) => {\n    const index = this._entities.indexOf(entity);\n    if (index === -1 && this._predicate(entity)) {\n      this._entities.push(entity);\n      if (this.onEntityAdded.hasHandlers) {\n        entity.takeSnapshot(this._snapshot);\n        this.onEntityAdded.emit(this._snapshot);\n      }\n    }\n  };\n\n  /**\n   * @internal\n   */\n  public entityRemoved = (entity: Entity) => {\n    const index = this._entities.indexOf(entity);\n    if (index !== -1) {\n      this._entities.splice(index, 1);\n      if (this.onEntityRemoved.hasHandlers) {\n        entity.takeSnapshot(this._snapshot);\n        this.onEntityRemoved.emit(this._snapshot);\n      }\n    }\n  };\n\n  /**\n   * @internal\n   */\n  public entityComponentAdded = <T>(entity: Entity, componentOrTag: NonNullable<T>, componentClass?: Class<NonNullable<T>>) => {\n    const hasAddedHandlers = this.onEntityAdded.hasHandlers;\n    const hasRemovedHandlers = this.onEntityRemoved.hasHandlers;\n\n    const index = this._entities.indexOf(entity);\n    const isMatch = this._predicate(entity);\n    if (index === -1 && isMatch) {\n      this._entities.push(entity);\n      if (hasAddedHandlers) {\n        entity.takeSnapshot(this._snapshot, componentOrTag, componentClass);\n        this.onEntityAdded.emit(this._snapshot);\n      }\n    } else if (index !== -1 && !isMatch) {\n      this._entities.splice(index, 1);\n      if (hasRemovedHandlers) {\n        entity.takeSnapshot(this._snapshot, componentOrTag, componentClass);\n        this.onEntityRemoved.emit(this._snapshot);\n      }\n    }\n  };\n\n  /**\n   * @internal\n   */\n  public entityComponentRemoved = <T>(entity: Entity, component: NonNullable<T>, componentClass?: Class<NonNullable<T>>) => {\n    const hasAddedHandlers = this.onEntityAdded.hasHandlers;\n    const hasRemovedHandlers = this.onEntityRemoved.hasHandlers;\n\n    const index = this._entities.indexOf(entity);\n    const isMatch = this._predicate(entity);\n    if (index !== -1 && !isMatch) {\n      this._entities.splice(index, 1);\n      if (hasRemovedHandlers) {\n        entity.takeSnapshot(this._snapshot, component, componentClass);\n        this.onEntityRemoved.emit(this._snapshot);\n      }\n    } else if (index === -1 && isMatch) {\n      this._entities.push(entity);\n      if (hasAddedHandlers) {\n        entity.takeSnapshot(this._snapshot, component, componentClass);\n        this.onEntityAdded.emit(this._snapshot);\n      }\n    }\n  };\n}\n\nfunction hasAll(entity: Entity, components: Set<number>, tags: Set<Tag>): boolean {\n  if (components.size > 0) {\n    for (const componentId of components) {\n      if (entity.components[componentId] === undefined) {\n        return false;\n      }\n    }\n  }\n  if (tags.size > 0) {\n    for (const tag of tags) {\n      if (!entity.tags.has(tag)) {\n        return false;\n      }\n    }\n  }\n  return true;\n}\n\n/**\n * Query builder, helps to create queries\n * @example\n * const query = new QueryBuilder()\n *  .contains(Position)\n *  .contains(Acceleration)\n *  .contains(TorqueForce)\n *  .build();\n */\nexport class QueryBuilder {\n  private readonly _components: Set<number> = new Set();\n  private readonly _tags: Set<Tag> = new Set();\n\n  /**\n   * Specifies components that must be added to entity to be matched\n   * @param componentsOrTags\n   */\n  public contains(...componentsOrTags: Array<any>): QueryBuilder {\n    for (const componentOrTag of componentsOrTags) {\n      if (isTag(componentOrTag)) {\n        if (!this._tags.has(componentOrTag)) {\n          this._tags.add(componentOrTag);\n        }\n      } else {\n        const componentId = getComponentId(componentOrTag, true)!;\n        if (!this._components.has(componentId)) {\n          this._components.add(componentId);\n        }\n      }\n    }\n    return this;\n  }\n\n  /**\n   * Build query\n   */\n  public build(): Query {\n    return new Query((entity: Entity) => hasAll(entity, this._components, this._tags));\n  }\n\n  /**\n   * @internal\n   */\n  public getComponents(): ReadonlySet<number> {\n    return this._components;\n  }\n\n  /**\n   * @internal\n   */\n  public getTags(): ReadonlySet<Tag> {\n    return this._tags;\n  }\n}\n\n/**\n * @internal\n */\nexport function isQueryPredicate(item: unknown): item is QueryPredicate {\n  return typeof item === 'function';\n}\n\n/**\n * @internal\n */\nexport function isQueryBuilder(item: unknown): item is QueryBuilder {\n  return item instanceof QueryBuilder;\n}\n"
  },
  {
    "path": "src/ecs/ReactionSystem.ts",
    "content": "import {isQueryBuilder, isQueryPredicate, Query, QueryBuilder, QueryPredicate} from './Query';\nimport {Engine} from './Engine';\nimport {Entity, EntitySnapshot} from './Entity';\nimport {System} from './System';\n\n/**\n * Represents a system that reacts when entities are added to or removed from its query.\n * `entityAdded` and `entityRemoved` will be called accordingly.\n *\n * @example\n * ```ts\n * class ViewSystem extends ReactionSystem {\n *   constructor(\n *      private readonly container:Container\n *   ) {\n *      super(new Query((entity:Entity) => entity.has(View));\n *   }\n *\n *   // Add entity view to the screen\n *   entityAdded = ({entity}:EntitySnapshot) => {\n *    this.container.add(entity.get(View)!.view);\n *   }\n *\n *   // Remove entity view from screen\n *   entityRemoved = (snapshot:EntitySnapshot) => {\n *    this.container.remove(snapshot.get(View)!.view);\n *   }\n * }\n * ```\n */\nexport abstract class ReactionSystem extends System {\n  protected readonly query: Query;\n\n  protected constructor(query: Query | QueryBuilder | QueryPredicate) {\n    super();\n    if (isQueryBuilder(query)) {\n      this.query = query.build();\n    } else if (isQueryPredicate(query)) {\n      this.query = new Query(query);\n    } else {\n      this.query = query;\n    }\n  }\n\n  protected get entities(): ReadonlyArray<Entity> {\n    return this.query.entities;\n  }\n\n  public onAddedToEngine() {\n    this.engine.addQuery(this.query);\n    this.prepare();\n    this.query.onEntityAdded.connect(this.entityAdded);\n    this.query.onEntityRemoved.connect(this.entityRemoved);\n  }\n\n  public onRemovedFromEngine() {\n    this.engine.removeQuery(this.query);\n    this.query.onEntityAdded.disconnect(this.entityAdded);\n    this.query.onEntityRemoved.disconnect(this.entityRemoved);\n    this.query.clear();\n  }\n\n  protected prepare() {}\n\n  /**\n   * Method will be called for every new entity that matches system query.\n   * You could easily override it with your own logic.\n   *\n   * Note: Method will not be called for already existing in query entities (at the adding system to engine phase),\n   * only new entities will be handled\n   *\n   * @param entity EntitySnapshot that contains entity that was removed from query or engine, and components that it has\n   *   before adding, and component that will be added\n   */\n  protected entityAdded = (entity: EntitySnapshot) => {\n  };\n\n  /**\n   * Method will be called for every entity matches system query, that is going to be removed from engine, or it stops\n   * matching to the query.\n   * You could easily override it with your own logic.\n   *\n   * @param entity EntitySnapshot that contains entity that was removed from query or engine, and components that it has\n   *   before removing\n   */\n  protected entityRemoved = (entity: EntitySnapshot) => {\n  };\n}\n"
  },
  {
    "path": "src/ecs/Subscription.ts",
    "content": "import {Class} from '../utils/Class';\n\n/**\n * @internal\n */\nexport class Subscription<T> {\n  public constructor(\n    public readonly messageType: Class<T> | T,\n    public readonly handler: (message: T) => void,\n  ) {}\n\n  public equals(messageType: Class<T> | T, handler?: (message: T) => void) {\n    return this.messageType === messageType && (handler === undefined || this.handler === handler);\n  }\n}\n"
  },
  {
    "path": "src/ecs/System.ts",
    "content": "import {Engine} from './Engine';\nimport {Entity} from './Entity';\n\n/**\n * Systems are logic bricks in your application.\n * If you want to manipulate entities and their components - it is the right place for that.\n */\nexport abstract class System {\n  private _priority: number = 0;\n  private _engine?: Engine;\n  private _isRemovalRequested: boolean = false;\n\n  /**\n   * Gets an {@link Engine} instance that system attached to\n   * @returns {Engine}\n   * @throws An error if system is not attached to the engine\n   */\n  public get engine(): Engine {\n    if (this._engine === undefined) throw new Error(`Property \"engine\" can't be accessed when system is not added to the engine`);\n    return this._engine;\n  }\n\n  /**\n   * Indicates that system should be removed from engine at the end of the current update cycle\n   * @internal\n   * @returns {boolean}\n   */\n  public get isRemovalRequested(): boolean {\n    return this._isRemovalRequested;\n  }\n\n  /**\n   * Gets an {@link Entity} instance that is shared across all systems and can be used as a config.\n   * @return {Entity}\n   */\n  protected get sharedConfig(): Entity {\n    if (this._engine === undefined) throw new Error(`Property \"sharedConfig\" can't be accessed when system is not added to the engine`);\n    return this._engine.sharedConfig;\n  }\n\n  /**\n   * Gets a priority of the system\n   */\n  public get priority(): number {\n    return this._priority;\n  }\n\n  /**\n   * All logic aimed at making changes in entities and their components must be placed in this method.\n   * @param dt - The time in seconds it took from previous update call.\n   */\n  public update(dt: number) {}\n\n  /**\n   * This method will be called after the system will be added to the Engine.\n   */\n  public onAddedToEngine() {}\n\n  /**\n   * Callback that will be invoked after removing system from engine\n   */\n  public onRemovedFromEngine() {}\n\n  /**\n   * Dispatches a message, that can be caught via {@link Engine#subscribe}.\n   * It's the best way to send a message outside. This mechanism allows you not to invent the signals/dispatchers\n   * mechanism for your systems, to report an event. For example, you can dispatch that the game round has been\n   * completed.\n   *\n   * @param {T} message\n   * @throws An error if system is not attached to the engine\n   * @example\n   * ```ts\n   * class RoundCompleted {\n   *   public constructor(\n   *      public readonly win:boolean\n   *   ) {}\n   * }\n   *\n   * const engine = new Engine();\n   * engine.subscribe(RoundCompleted, (message:RoundCompleted) => {\n   *   if (message.win) {\n   *     this.showWinDialog();\n   *   } else {\n   *     this.showLoseDialog();\n   *   }\n   * })\n   *\n   * class RoundCompletionSystem extends System {\n   *   public update(dt:number) {\n   *     if (heroesQuery.isEmpty) {\n   *       this.dispatch(new RoundCompleted(false));\n   *     } else if (enemiesQuery.isEmpty) {\n   *       this.dispatch(new RoundCompleted(true));\n   *     }\n   *   }\n   * }\n   * ```\n   */\n  public dispatch<T>(message: T): void {\n    if (this._engine === undefined) {\n      throw new Error('Dispatching a message can\\'t be done while system is not attached to the engine');\n    }\n    this.engine.dispatch(message);\n  }\n\n  /**\n   * @internal\n   */\n  public setEngine(engine: Engine | undefined): void {\n    this._engine = engine;\n  }\n\n  /**\n   * @internal\n   */\n  public setPriority(priority: number): void {\n    this._priority = priority;\n  }\n\n  protected requestRemoval(): void {\n    this._isRemovalRequested = true;\n  }\n}\n"
  },
  {
    "path": "src/ecs/Tag.ts",
    "content": "/**\n * A tag is a simple marker that can be considered as a component without data.\n * It can be used instead of creating a new component class, when you don't need an additional data.\n */\nexport type Tag = number | string;\n\n/**\n * This predicate can help you to understand whether item is a component or tag\n * @param item\n * @returns {item is Tag}\n */\nexport function isTag(item: unknown): item is Tag {\n  const type = typeof item;\n  return type === 'string' || type === 'number';\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "export * from './utils/Class';\nexport * from './utils/Signal';\nexport * from './ecs/ComponentId';\nexport * from './ecs/Tag';\nexport * from './ecs/LinkedComponent';\nexport * from './ecs/Engine';\nexport * from './ecs/Entity';\nexport * from './ecs/System';\nexport * from './ecs/Query';\nexport * from './ecs/IterativeSystem';\nexport * from './ecs/ReactionSystem';"
  },
  {
    "path": "src/utils/Class.ts",
    "content": "export type Class<T> = {\n  new(...args: any[]): T;\n};\n\n"
  },
  {
    "path": "src/utils/Signal.ts",
    "content": "/**\n * Lightweight implementation of Signal\n */\nexport class Signal<Handler extends (...args: any[]) => any> {\n  private readonly handlers: SignalHandler<Handler>[] = [];\n\n  /**\n   * Gets a value that indicates whether signal has handlers\n   * @return {boolean}\n   */\n  public get hasHandlers(): boolean {\n    return this.handlers.length > 0;\n  }\n\n  /**\n   * Gets an amount of connected handlers\n   * @return {number}\n   */\n  public get handlersAmount(): number {\n    return this.handlers.length;\n  }\n\n  /**\n   * Connects signal handler, that will be invoked on signal emit.\n   * @param {Handler} handler\n   * @param priority Handler invocation priority (handler with higher priority will be called later than with lower one)\n   */\n  public connect(handler: Handler, priority: number = 0): void {\n    const existingHandler = this.handlers.find((it) => it.equals(handler));\n    let needResort: boolean;\n    if (existingHandler !== undefined) {\n      needResort = existingHandler.priority !== priority;\n      existingHandler.priority = priority;\n    } else {\n      const lastHandler = this.handlers[this.handlers.length - 1];\n      this.handlers.push(new SignalHandler(handler, priority));\n      needResort = (lastHandler !== undefined && lastHandler.priority > priority);\n    }\n    if (needResort) {\n      this.handlers.sort((a, b) => a.priority - b.priority);\n    }\n  }\n\n  /**\n   * Disconnects signal handler\n   * @param {Handler} handler\n   */\n  public disconnect(handler: Handler): void {\n    const existingHandlerIndex = this.handlers.findIndex((it) => it.equals(handler));\n    if (existingHandlerIndex >= 0) {\n      this.handlers.splice(existingHandlerIndex, 1);\n    }\n  }\n\n  /**\n   * Disconnects all signal handlers\n   * @param {Handler} handler\n   */\n  public disconnectAll(): void {\n    this.handlers.length = 0;\n  }\n\n  /**\n   * Invokes connected handlers with passed parameters.\n   * @param {any} args\n   */\n  public emit(...args: Parameters<Handler>): void {\n    for (const handler of this.handlers) {\n      handler.handle(...args);\n    }\n  }\n}\n\nclass SignalHandler<Handler extends (...args: any[]) => any> {\n  public constructor(public readonly handler: Handler, public priority: number) {}\n\n  public equals(handler: Handler): boolean {\n    return this.handler === handler;\n  }\n\n  public handle(...args: any[]) {\n    this.handler(...args);\n  }\n}\n"
  },
  {
    "path": "tests/unit/engine.spec.ts",
    "content": "import {Engine, Entity, IterativeSystem, LinkedComponent, Query, QueryBuilder, QueryPredicate, System, ReactionSystem } from '../../src';\n\nclass Component {}\n\nclass Message {}\n\nconst handler1 = (message: Message) => {};\nconst handler2 = (message: Message) => {};\nconst handler3 = (message: Message) => {};\n\nabstract class TestSystem extends IterativeSystem {\n  protected constructor(\n    query: Query | QueryBuilder | QueryPredicate,\n    private readonly arr?: number[],\n  ) {\n    super(query);\n    this.arr = arr;\n  }\n\n  public update(dt: number) {\n    super.update(dt);\n    if (this.arr !== undefined) {\n      this.arr.push(this.priority);\n    }\n  }\n\n  protected updateEntity(entity: Entity, dt: number): void {\n  }\n}\n\nclass TestSystem1 extends TestSystem {\n  public constructor(arr?: number[]) {\n    super(new Query((entity: Entity) => entity.has(Component)), arr);\n  }\n}\n\nclass TestSystem2 extends TestSystem {\n  public constructor(arr?: number[]) {\n    super((entity: Entity) => entity.has(Component), arr);\n  }\n}\n\nclass TestSystem3 extends TestSystem {\n  public constructor(arr?: number[]) {\n    super(new QueryBuilder().contains(Component), arr);\n  }\n}\n\ndescribe('System manipulation', () => {\n  it('Engine system creating', () => {\n    const engine = new Engine();\n    expect(engine.systems).toBeDefined();\n    expect(engine.systems.length).toBe(0);\n    expect(engine.entities).toBeDefined();\n    expect(engine.entities.length).toBe(0);\n    expect(engine.queries).toBeDefined();\n    expect(engine.queries.length).toBe(0);\n  });\n\n  it('Adding system', () => {\n    const engine = new Engine();\n    const system = new TestSystem1();\n\n    engine.addSystem(system);\n\n    expect(engine.systems.length).toBe(1);\n    expect(engine.getSystem(TestSystem1)).toBe(system);\n  });\n\n  it('Adding and removing multiple system with priority', () => {\n    const engine = new Engine();\n    const system1 = new TestSystem1();\n    const system2 = new TestSystem2();\n    const system3 = new TestSystem3();\n\n    engine.addSystem(system1, 200);\n    engine.addSystem(system2, 300);\n    engine.addSystem(system3, 100);\n\n    expect(engine.systems.length).toBe(3);\n    expect(engine.getSystem(TestSystem1)).toBe(system1);\n    expect(engine.getSystem(TestSystem2)).toBe(system2);\n    expect(engine.getSystem(TestSystem3)).toBe(system3);\n    expect(engine.systems).toEqual([system3, system1, system2]);\n\n    engine.removeAllSystems();\n\n    expect(engine.systems.length).toBe(0);\n  });\n\n  it('Adding multiple systems with same priority must added in same order', () => {\n    const engine = new Engine();\n    const system1 = new TestSystem1();\n    const system2 = new TestSystem2();\n    const system3 = new TestSystem3();\n\n    engine.addSystem(system1);\n    engine.addSystem(system2);\n    engine.addSystem(system3);\n\n    expect(engine.systems.length).toBe(3);\n    expect(engine.systems).toEqual([system1, system2, system3]);\n  });\n\n  it('Remove system', () => {\n    const engine = new Engine();\n    const system = new TestSystem1();\n\n    engine.addSystem(system);\n\n    expect(engine.systems.length).toBe(1);\n    expect(engine.getSystem(TestSystem1)).toBe(system);\n\n    engine.removeSystem(system);\n\n    expect(engine.systems.length).toBe(0);\n    expect(engine.getSystem(TestSystem1)).toBeUndefined();\n  });\n\n  it(`Expected that removing not attached system will not throw an error`, () => {\n    const engine = new Engine();\n    const system = new TestSystem1();\n    expect(() => { engine.removeSystem(system);}).not.toThrowError();\n  });\n\n  it('Engine updating', () => {\n    const engine = new Engine();\n    const arr: number[] = [];\n    const system1 = new TestSystem1(arr);\n    const system2 = new TestSystem2(arr);\n    const system3 = new TestSystem3(arr);\n\n    engine.addSystem(system1, 1);\n    engine.addSystem(system2, 2);\n    engine.addSystem(system3, 3);\n\n    engine.update(1);\n    expect(arr).toEqual([1, 2, 3]);\n  });\n\n  it('Engine#clear should remove entities, systems, remove and clear queries', () => {\n    class TestSystem extends IterativeSystem {\n      public constructor() {\n        super(new Query(entity => true));\n      }\n\n      protected updateEntity(entity: Entity, dt: number): void {\n      }\n    }\n\n    const engine = new Engine();\n    const query = new Query(entity => entity.has(Component));\n    const system = new TestSystem();\n\n    engine.addQuery(query);\n    engine.addSystem(system);\n    engine.addEntity(new Entity().add(new Component()));\n\n    expect(query.isEmpty).toBeFalsy();\n\n    engine.clear();\n    expect(engine.systems.length).toBe(0);\n    expect(engine.queries.length).toBe(0);\n    expect(engine.entities.length).toBe(0);\n    expect(query.isEmpty).toBeTruthy();\n\n    engine.addEntity(new Entity().add(new Component()));\n    expect(query.isEmpty).toBeTruthy();\n  });\n\n  it('Expected that removing all entities will fire onEntityRemoved', () => {\n    const engine = new Engine();\n    const entitiesCount = 2;\n    let removedCount = 0;\n    for (let i = 0; i < entitiesCount; i++) {\n      engine.addEntity(new Entity());\n    }\n    engine.onEntityRemoved.connect(() => removedCount++);\n    engine.removeAllEntities();\n    expect(engine.entities.length).toBe(0);\n    expect(removedCount).toBe(entitiesCount);\n  });\n\n  it(`Expected that engine will not add same handler twice for the same message`, () => {\n    const engine = new Engine();\n    const handler = (message: Message) => {};\n    const subscription1 = engine.subscribe(Message, handler);\n    const subscription2 = engine.subscribe(Message, handler);\n    expect(subscription1).toBe(subscription2);\n  });\n\n  it(`Expected that unsubscribe removes specific subscription`, () => {\n    const engine = new Engine();\n    engine.subscribe(Message, handler1);\n    engine.subscribe(Message, handler2);\n    engine.subscribe(Message, handler3);\n    expect(engine.subscriptions.length).toBe(3);\n    engine.unsubscribe(Message, handler1);\n    expect(engine.subscriptions.length).toBe(2);\n  });\n\n  it(`Expected that unsubscribe removes all relevant subscriptions`, () => {\n    const engine = new Engine();\n    engine.subscribe(Message, handler1);\n    engine.subscribe(Message, handler2);\n    engine.subscribe(Message, handler3);\n    expect(engine.subscriptions.length).toBe(3);\n    engine.unsubscribe(Message);\n    expect(engine.subscriptions.length).toBe(0);\n  });\n\n  it(`Expected that unsubscribeAll removes all subscriptions`, () => {\n    const engine = new Engine();\n    engine.subscribe(Message, handler1);\n    engine.subscribe(Message, handler2);\n    engine.subscribe(Message, handler3);\n    expect(engine.subscriptions.length).toBe(3);\n    engine.unsubscribeAll();\n    expect(engine.subscriptions.length).toBe(0);\n  });\n\n  it(`Expected that system\\`s message will be delivered through the engine to the handler`, () => {\n    const HERO = 'hero';\n    const GAME_OVER = 'gameOver';\n\n    class GameOverSystem extends ReactionSystem {\n      private dispatched: boolean = false;\n\n      public constructor() {\n        super((entity: Entity) => entity.has(HERO));\n      }\n\n      public update(dt: number) {\n        if (this.dispatched) return;\n\n        if (!this.query.isEmpty && !this.dispatched) {\n          this.dispatch(GAME_OVER);\n          this.dispatched = true;\n        }\n      }\n\n      protected prepare() {\n        this.dispatched = false;\n      }\n    }\n\n    let gameOverReceived = false;\n    const engine = new Engine();\n    const system = new GameOverSystem();\n    engine.subscribe(GAME_OVER, () => { gameOverReceived = true; });\n    engine.addSystem(system);\n    engine.addEntity(new Entity().add(HERO));\n    engine.addEntity(new Entity().add(HERO));\n    engine.update(1);\n    engine.removeAllEntities();\n    engine.update(1);\n    expect(gameOverReceived).toBeTruthy();\n  });\n\n  it(`Expected that system\\`s message will be delivered through the engine to the handler`, () => {\n    const HERO = 'hero';\n\n    class GameOver {}\n\n    class OtherMessage {}\n\n    class GameOverSystem extends ReactionSystem {\n      private dispatched: boolean = false;\n\n      public constructor() {\n        super((entity: Entity) => entity.has(HERO));\n      }\n\n      public update(dt: number) {\n        if (this.dispatched) return;\n\n        if (!this.query.isEmpty && !this.dispatched) {\n          this.dispatch(new GameOver());\n          this.dispatched = true;\n        }\n      }\n\n      protected prepare() {\n        this.dispatched = false;\n      }\n    }\n\n    let gameOverReceived = false;\n    let otherMessageReceived = false;\n    const engine = new Engine();\n    const system = new GameOverSystem();\n    engine.subscribe(GameOver, () => { gameOverReceived = true; });\n    engine.subscribe(OtherMessage, () => { otherMessageReceived = true; });\n    engine.addSystem(system);\n    engine.addEntity(new Entity().add(HERO));\n    engine.addEntity(new Entity().add(HERO));\n    engine.update(1);\n    engine.removeAllEntities();\n    engine.update(1);\n    expect(gameOverReceived).toBeTruthy();\n    expect(otherMessageReceived).toBeFalsy();\n  });\n\n  it(`Expected that removing of not attached query will not throw an error`, () => {\n    const TAG = 1;\n    const query = new Query((entity: Entity) => entity.has(TAG));\n    const engine = new Engine();\n    expect(() => {engine.removeQuery(query);}).not.toThrowError();\n  });\n\n  it(`Expected that adding the same entity twice will add it only once`, () => {\n    const entity = new Entity();\n    const engine = new Engine();\n    engine.addEntity(entity);\n    engine.addEntity(entity);\n    expect(engine.entities.length).toBe(1);\n  });\n\n  it(`Expected that removing an entity that wasn't added to engine will do nothing`, () => {\n    const entity1 = new Entity();\n    const entity2 = new Entity();\n    const engine = new Engine();\n    engine.addEntity(entity1);\n    engine.removeEntity(entity2);\n    let entityRemovedCount = 0;\n    engine.onEntityRemoved.connect((entity) => {\n      entityRemovedCount++;\n    });\n    expect(engine.entities.length).toBe(1);\n    expect(engine.entities[0]).toBe(entity1);\n    expect(entityRemovedCount).toBe(0);\n  });\n\n  it('Getting entity by id from engine should success if entity is in the engine', () => {\n    const engine = new Engine();\n    const entity = new Entity();\n    const id = entity.id;\n    engine.addEntity(entity);\n    expect(engine.getEntityById(id)).toBe(entity);\n  });\n\n  it('Getting entity by id from engine should fail if entity is not in the engine', () => {\n    const engine = new Engine();\n    const entity = new Entity();\n    const id = entity.id;\n    engine.addEntity(entity);\n    engine.removeEntity(entity);\n    expect(engine.getEntityById(id)).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "tests/unit/entity.spec.ts",
    "content": "import {Entity, EntitySnapshot, getComponentId, LinkedComponent} from '../../src';\n\nclass Position {\n  public x: number = 0;\n  public y: number = 0;\n\n  constructor(x: number = 0, y: number = 0) {\n    this.x = x;\n    this.y = y;\n  }\n}\n\nclass Damage extends LinkedComponent {\n  public constructor(\n    public value: number,\n    id?: string,\n  ) {\n    super(id);\n  }\n}\n\nclass AnotherDamage extends LinkedComponent {\n  public constructor() {\n    super();\n  }\n}\n\nclass DamageChild extends Damage {\n  public constructor() {\n    super(1);\n  }\n}\n\ndescribe('Components id', () => {\n  it('Getting component id without forcing of id creation returns undefined', () => {\n    expect(getComponentId(\n      class Test {\n      },\n    )).toBeUndefined();\n  });\n\n  it('Getting component id return equal values for same component twice', () => {\n    class Test1 {\n    }\n\n    class Test2 {\n    }\n\n    expect(getComponentId(Test1, true))\n      .toBe(getComponentId(Test1, true));\n\n    expect(getComponentId(Test2, true))\n      .toBe(getComponentId(Test2));\n  });\n\n  it('Getting components id returns different values', () => {\n    class Test1 {\n    }\n\n    class Test2 {\n    }\n\n    const positionId = getComponentId(Test1, true);\n    const viewId = getComponentId(Test2, true);\n\n    expect(positionId).toBeDefined();\n    expect(viewId).toBeDefined();\n\n    expect(positionId == viewId).toBeFalsy();\n  });\n});\n\ndescribe('Components and Tags', () => {\n  it('Adding single component, must to dispatch only onComponentAdded once', () => {\n    const entity = new Entity();\n    let addedCount = 0;\n    let removedCount = 0;\n\n    expect(entity.has(Position)).toBe(false);\n\n    const addedCallback = () => addedCount++;\n    const removedCallback = () => removedCount++;\n    entity.onComponentAdded.connect(addedCallback);\n    entity.onComponentRemoved.connect(removedCallback);\n\n    entity.add(new Position());\n\n    entity.onComponentAdded.disconnect(addedCallback);\n    entity.onComponentRemoved.disconnect(removedCallback);\n\n    expect(entity.has(Position)).toBe(true);\n    expect(addedCount).toBe(1);\n    expect(removedCount).toBe(0);\n  });\n\n  it('Adding component twice, must override previous component', () => {\n    const entity = new Entity();\n    let addedCount = 0;\n    let removedCount = 0;\n\n    const position1 = new Position(0, 0);\n    const position2 = new Position(1, 1);\n\n    const addedCallback = () => addedCount++;\n    const removedCallback = () => removedCount++;\n    entity.onComponentAdded.connect(addedCallback);\n    entity.onComponentRemoved.connect(removedCallback);\n\n    entity.add(position1);\n    entity.add(position2);\n\n    entity.onComponentAdded.disconnect(addedCallback);\n    entity.onComponentRemoved.disconnect(removedCallback);\n\n    expect(entity.get(Position)).toBe(position2);\n    expect(entity.getComponents().length).toBe(1);\n    expect(addedCount).toBe(2);\n    expect(removedCount).toBe(1);\n  });\n\n  it(`Adding the same component, must not trigger onComponentAdded for the second call`, () => {\n    const entity = new Entity();\n    let addedCount = 0;\n    let removedCount = 0;\n\n    const position = new Position(0, 0);\n\n    const addedCallback = () => addedCount++;\n    const removedCallback = () => removedCount++;\n    entity.onComponentAdded.connect(addedCallback);\n    entity.onComponentRemoved.connect(removedCallback);\n\n    entity.add(position);\n    entity.add(position);\n\n    entity.onComponentAdded.disconnect(addedCallback);\n    entity.onComponentRemoved.disconnect(removedCallback);\n\n    expect(entity.get(Position)).toBe(position);\n    expect(entity.getComponents().length).toBe(1);\n    expect(addedCount).toBe(1);\n    expect(removedCount).toBe(0);\n  });\n\n  it(`Adding component with 'resolve class' ancestor`, () => {\n    class Ancestor {}\n\n    class Descendant extends Ancestor {}\n\n    class Descendant2 extends Ancestor {}\n\n    const entity = new Entity();\n    entity.add(new Descendant(), Ancestor);\n    const id1 = getComponentId(Ancestor);\n    const id2 = getComponentId(Descendant);\n\n    expect(id1).not.toEqual(id2);\n\n    expect(entity.has(Ancestor)).toBeTruthy();\n    expect(entity.get(Ancestor)).toBeDefined();\n\n    expect(entity.has(Descendant)).toBeFalsy();\n    expect(entity.get(Descendant)).toBeUndefined();\n\n    expect(entity.has(Descendant2)).toBeFalsy();\n    expect(entity.get(Descendant2)).toBeUndefined();\n  });\n\n  it(`Adding component with 'resolve class' not ancestor`, () => {\n    class Ancestor {}\n\n    class Descendant extends Ancestor {}\n\n    class Other {}\n\n    const entity = new Entity();\n    expect(\n      () => { entity.add(new Ancestor(), Descendant); },\n    ).toThrow();\n\n    expect(\n      () => { entity.add(new Ancestor(), Other); },\n    ).toThrow();\n\n  });\n\n  it(`Adding component of type Ancestor should override component with 'resolve class' Ancestor`, () => {\n    class Ancestor {}\n\n    class Descendant extends Ancestor {}\n\n    const entity = new Entity();\n    const ancestor = new Ancestor();\n    const descendant = new Descendant();\n    entity.add(descendant, Ancestor);\n    expect(entity.get(Ancestor)).toBe(descendant);\n\n    entity.add(ancestor);\n    expect(entity.has(Ancestor)).toBeTruthy();\n    expect(entity.get(Ancestor)).toBe(ancestor);\n  });\n\n  it('Expected that hasAny returns true from component', () => {\n    class Other {}\n\n    const entity = new Entity();\n    entity.add(new Position());\n    expect(entity.hasAny(Other, Position)).toBeTruthy();\n  });\n\n  it('Expected that hasAny returns false', () => {\n    class Other {}\n\n    class A {}\n\n    const TAG = 'tag';\n\n    const entity = new Entity();\n    entity.add(new A());\n    entity.add(TAG);\n    expect(entity.hasAny(Other, Position)).toBeFalsy();\n  });\n\n  it('Expected that hasAll returns true', () => {\n    const entity = new Entity();\n    const TAG = 12345;\n    entity.add(new Position());\n    entity.add(TAG);\n    expect(entity.hasAll(TAG, Position)).toBeTruthy();\n  });\n\n  it('Expected that hasAll returns false', () => {\n    class Other {}\n\n    const entity = new Entity();\n    entity.add(new Position());\n    expect(entity.hasAll(Other, Position)).toBeFalsy();\n  });\n\n  it(`Expected that adding a tag dispatches onComponentAdded once`, () => {\n    const TAG = 0;\n    let addedCount = 0;\n    let removedCount = 0;\n\n    const addedCallback = () => addedCount++;\n    const removedCallback = () => removedCount++;\n\n    const entity = new Entity();\n    entity.onComponentAdded.connect(addedCallback);\n    entity.onComponentRemoved.connect(removedCallback);\n    entity.add(TAG);\n\n    const tags = entity.getTags();\n    expect(addedCount).toBe(1);\n    expect(entity.tags.size).toBe(1);\n    expect(tags.length).toBe(1);\n    expect(removedCount).toBe(0);\n  });\n\n  it(`Expected that adding a tag twice dispatches onComponentAdded only once`, () => {\n    const TAG = 0;\n    let addedCount = 0;\n    let removedCount = 0;\n\n    const addedCallback = () => addedCount++;\n    const removedCallback = () => removedCount++;\n\n    const entity = new Entity();\n    entity.onComponentAdded.connect(addedCallback);\n    entity.onComponentRemoved.connect(removedCallback);\n    entity.add(TAG);\n    entity.add(TAG);\n\n    expect(addedCount).toBe(1);\n    expect(removedCount).toBe(0);\n  });\n\n  it(`Expected that entity has an added tag`, () => {\n    const TAG = 0;\n    const entity = new Entity();\n    entity.add(TAG);\n\n    expect(entity.has(TAG)).toBeTruthy();\n  });\n\n  it(`Expected that appending the same linked component twice will throw an error`, () => {\n    const entity = new Entity();\n    const damage = new Damage(10);\n    expect(() => {\n      entity.append(damage);\n      entity.append(damage);\n    }).toThrowError();\n  });\n\n  it(`Expected that specifying not ancestor as a resolve class for appended component throws an error`, () => {\n    const entity = new Entity();\n    expect(() => {\n      entity.append(new Damage(10), AnotherDamage);\n    }).toThrow();\n    expect(() => {\n      entity.append(new Damage(10), DamageChild);\n    }).toThrow();\n  });\n\n  it(`Expected that specifying resolve class for appended component gives right resolving`, () => {\n    const entity = new Entity();\n    const secondChild = new DamageChild();\n    const firstChild = new DamageChild();\n    entity.append(firstChild, Damage);\n    entity.append(secondChild, Damage);\n    expect(entity.get(Damage)).toEqual(firstChild);\n  });\n\n  it(`Expected that appending the same linked component twice with gaps will throw an error`, () => {\n    const entity = new Entity();\n    const damage = new Damage(10);\n    expect(() => {\n      entity.append(damage);\n      for (let i = 0; i < 5; i++) {\n        entity.append(new Damage(i));\n      }\n      entity.append(damage);\n    }).toThrowError();\n  });\n\n  it(`Expected that appending the two different instances of linked component will not throw an error`, () => {\n    const entity = new Entity();\n    expect(() => {\n      entity.append(new Damage(10));\n      entity.append(new Damage(10));\n    }).not.toThrowError();\n  });\n\n  it(`Expected that appending the two different instances of linked component will trigger onComponentAdded only once`, () => {\n    const entity = new Entity();\n    let addedAmount = 0;\n    entity.onComponentAdded.connect(() => { addedAmount++; });\n    entity.append(new Damage(10));\n    entity.append(new Damage(10));\n    expect(addedAmount).toBe(2);\n  });\n\n  it(`Removing linked component with \"remove\" removes whole linked list`, () => {\n    const entity = new Entity();\n    entity.append(new Damage(10));\n    entity.append(new Damage(10));\n    entity.remove(Damage);\n\n    expect(entity.get(Damage)).toBeUndefined();\n  });\n\n  it(`Removing linked component with \"pick\" removes only first component`, () => {\n    const entity = new Entity();\n    const damage1 = new Damage(1);\n    const damage2 = new Damage(2);\n    entity.append(damage1);\n    entity.append(damage2);\n    entity.pick(damage1);\n    expect(entity.get(Damage)).toBe(damage2);\n  });\n\n  it(`get LinkedComponent by id returns specific linked component instance`, () => {\n    const entity = new Entity();\n    const damage1 = new Damage(1);\n    const damage2 = new Damage(2, 'ka-boom');\n    entity.append(damage1);\n    entity.append(damage2);\n    expect(entity.get(Damage, 'ka-boom')).toBe(damage2);\n  });\n\n  it(`get regular component by id returns always undefined`, () => {\n    const entity = new Entity();\n    entity.add(new Position());\n    expect(entity.get(Position, 'ka-boom')).toBeUndefined();\n  });\n\n  it(`has LinkedComponent with id returns specific linked component instance`, () => {\n    const entity = new Entity();\n    const damage1 = new Damage(1);\n    const damage2 = new Damage(2, 'ka-boom');\n    entity.append(damage1);\n    entity.append(damage2);\n    expect(entity.has(Damage, 'ka-boom')).toBeTruthy();\n  });\n\n  it(`has regular Component with id always returns false`, () => {\n    const entity = new Entity();\n    const position = new Position();\n    entity.add(position);\n    expect(entity.has(Position, 'ka-boom')).toBeFalsy();\n  });\n\n  it(`\"pick\" by id removes component as expected`, () => {\n    const entity = new Entity();\n    const damage1 = new Damage(1);\n    const damage2 = new Damage(2, 'ka-boom');\n    entity.append(damage1);\n    entity.append(damage2);\n    const picked = entity.pick(Damage, 'ka-boom');\n    expect(damage2).toBe(picked);\n  });\n\n  it(`\"pick\" by id won't remove anything if component is not entity`, () => {\n    const entity = new Entity();\n    const damage1 = new Damage(1);\n    const damage2 = new Damage(2);\n    entity.append(damage1);\n    entity.append(damage2);\n    const picked = entity.pick(Damage, 'ka-boom');\n    expect(picked).toBeUndefined();\n  });\n\n  it(`Withdrawing all components clears linked list associated to component class`, () => {\n    const entity = new Entity()\n      .append(new Damage(1))\n      .append(new Damage(2))\n      .append(new Damage(3));\n\n    while (entity.has(Damage)) {\n      entity.withdraw(Damage);\n    }\n\n    expect(entity.has(Damage)).toBeFalsy();\n    expect(entity.getLinkedComponentList(Damage, false)).toBeUndefined();\n  });\n\n  it(`\"withdraw\" returns undefined if there is no linked components appended`, () => {\n    const entity = new Entity()\n      .add(new Position())\n      .append(new Damage(1))\n      .append(new Damage(2));\n\n    while (entity.has(Damage)) {\n      entity.withdraw(Damage);\n    }\n\n    expect(entity.withdraw(Damage)).toBeUndefined();\n  });\n\n  it('\"contains\" returns the same instance if it exists in the linked components appended to the Entity', () => {\n    const damage = new Damage(1);\n    const entity = new Entity()\n      .append(new Damage(1))\n      .append(damage)\n      .append(new Damage(2));\n\n    expect(entity.contains(damage)).toBeTruthy();\n  });\n\n  it('\"contains\" returns undefined linked component is not appended to the Entity', () => {\n    const damage = new Damage(1);\n    const entity = new Entity()\n      .append(new Damage(1))\n      .append(new Damage(2));\n\n    expect(entity.contains(damage)).toBeFalsy();\n  });\n\n  it('\"contains\" returns undefined for linked component registered under another resolveClass', () => {\n    const damage = new DamageChild();\n    const entity = new Entity()\n      .append(damage, Damage);\n\n    expect(entity.contains(damage, DamageChild)).toBeFalsy();\n  });\n\n  it('\"contains\" works for regular components', () => {\n    const position = new Position(1, 1);\n    const entity = new Entity()\n      .append(new Damage(1))\n      .append(new Damage(2))\n      .add(position);\n    expect(entity.contains(position)).toBeTruthy();\n  });\n\n  it(`Linked components must be cleared after remove`, () => {\n    const entity = new Entity();\n    entity.append(new Damage(1));\n    entity.append(new Damage(2));\n    entity.remove(Damage);\n    entity.append(new Damage(3));\n    expect(entity.lengthOf(Damage)).toBe(1);\n  });\n\n  it(`Find component returns linked component instance accepted by predicate`, () => {\n    const entity = new Entity();\n    const damage1 = new Damage(1);\n    const damage2 = new Damage(2);\n    entity\n      .append(damage1)\n      .append(damage2);\n    expect(entity.find(Damage, (it) => it.value === 2)).toBe(damage2);\n  });\n\n  it(`Find component returns regular component instance accepted by predicate`, () => {\n    const entity = new Entity();\n    entity.append(new Damage(1))\n          .add(new Position(100, 100));\n    expect(entity.find(Position, (it) => it.x === 100 && it.y === 100)).toBe(entity.get(Position));\n  });\n\n  it('Entity.linkedComponents returns all linked components instances for specific component class', () => {\n    const entity = new Entity();\n    entity\n      .append(new Damage(1))\n      .append(new Damage(2))\n      .append(new Damage(3));\n    let amount = 0;\n    for (const damage of entity.getAll(Damage)) {\n      if (damage.value === amount + 1) {\n        amount++;\n      }\n    }\n    expect(amount).toBe(3);\n  });\n});\n\ndescribe('Removing component', () => {\n  it('Simple', () => {\n    const entity = new Entity();\n    const position = new Position(1, 1);\n\n    let addedCount = 0;\n    let removedCount = 0;\n\n    const addedCallback = () => addedCount++;\n    const removedCallback = () => removedCount++;\n    entity.onComponentAdded.connect(addedCallback);\n    entity.onComponentRemoved.connect(removedCallback);\n\n    entity.add(position);\n    const removedComponent = entity.remove(Position);\n\n    entity.onComponentAdded.disconnect(addedCallback);\n    entity.onComponentRemoved.disconnect(removedCallback);\n\n    expect(entity.getComponents().length).toBe(0);\n    expect(addedCount).toBe(1);\n    expect(removedCount).toBe(1);\n    expect(removedComponent).toBeDefined();\n    expect(removedComponent).toBe(position);\n  });\n\n  it('Removing absent component', () => {\n    const entity = new Entity();\n\n    let addedCount = 0;\n    let removedCount = 0;\n\n    const addedCallback = () => addedCount++;\n    const removedCallback = () => removedCount++;\n    entity.onComponentAdded.connect(addedCallback);\n    entity.onComponentRemoved.connect(removedCallback);\n\n    const removedComponent = entity.remove(Position);\n\n    entity.onComponentAdded.disconnect(addedCallback);\n    entity.onComponentRemoved.disconnect(removedCallback);\n\n    expect(entity.getComponents().length).toBe(0);\n    expect(addedCount).toBe(0);\n    expect(removedCount).toBe(0);\n    expect(removedComponent).toBeUndefined();\n  });\n\n  it(`Expected that entity doesn't have removed tag`, () => {\n    const TAG = 0;\n    const entity = new Entity();\n    entity.add(TAG);\n    entity.remove(TAG);\n    expect(entity.has(TAG)).toBeFalsy();\n  });\n\n  it(`Expected that removing absent tag returns undefined`, () => {\n    const TAG = 1234;\n    const entity = new Entity();\n    expect(entity.remove(TAG)).toBeUndefined();\n  });\n\n  it(`\"withdraw\" can remove regular component as well`, () => {\n    const entity = new Entity();\n    const position = new Position(1, 1);\n    const result = entity\n      .add(position)\n      .withdraw(Position);\n    expect(result).toBe(position);\n    expect(entity.has(Position)).toBeFalsy();\n  });\n\n  it(`\"pick\" can remove regular component as well`, () => {\n    const entity = new Entity();\n    const position = new Position(1, 1);\n    const result = entity\n      .add(position)\n      .pick(position);\n    expect(result).toBe(position);\n    expect(entity.has(Position)).toBeFalsy();\n  });\n});\n\ndescribe('Snapshot', () => {\n  it(`Expected that checking tag in the blank snapshot gives false`, () => {\n    const TAG = 1;\n    const snapshot = new EntitySnapshot();\n    expect(snapshot.previous.has(TAG)).toBeFalsy();\n  });\n\n  it('Expect undefined value (but not throwing an error) for getting component instance, if snapshot not initialized', () => {\n    class Component {}\n\n    const snapshot = new EntitySnapshot();\n    expect(() => snapshot.previous.get(Component)).not.toThrowError();\n    expect(snapshot.previous.get(Component)).toBeUndefined();\n  });\n\n  it('Expect undefined value for class that was not being initialized as component', () => {\n    class Component {}\n\n    class NotAComponent {}\n\n    const entity = new Entity();\n    entity.add(new Component());\n\n    const snapshot = new EntitySnapshot();\n    entity.takeSnapshot(snapshot, new Component());\n    expect(() => snapshot.previous.get(NotAComponent)).not.toThrowError();\n    expect(snapshot.previous.get(NotAComponent)).toBeUndefined();\n  });\n\n  it(`Expected that added component appears in current state, but not in the previous`, () => {\n    class ComponentA {}\n\n    class ComponentB {}\n\n    const TAG_C = 'tag-c';\n\n    const snapshot = new EntitySnapshot();\n    const entity = new Entity().add(new ComponentA());\n    entity.onComponentAdded.connect((entity, componentOrTag) => {\n      entity.takeSnapshot(snapshot, componentOrTag);\n    });\n\n    {\n      entity.add(new ComponentB());\n      expect(snapshot.current.has(ComponentB)).toBeTruthy();\n      expect(snapshot.current.get(ComponentB)).toBeDefined();\n      expect(snapshot.previous.has(ComponentB)).toBeFalsy();\n      expect(snapshot.previous.get(ComponentB)).toBeUndefined();\n    }\n    {\n      entity.add(TAG_C);\n      expect(snapshot.current.has(TAG_C)).toBeTruthy();\n      expect(snapshot.previous.has(TAG_C)).toBeFalsy();\n    }\n  });\n\n  it(`Expected that removed component appears in previous state, but not in the current`, () => {\n    class ComponentA {}\n\n    const TAG_C = 'tag-c';\n\n    const snapshot = new EntitySnapshot();\n    const entity = new Entity().add(new ComponentA()).add(TAG_C);\n    entity.onComponentRemoved.connect((entity, componentOrTag) => {\n      entity.takeSnapshot(snapshot, componentOrTag);\n    });\n\n    {\n      entity.remove(ComponentA);\n      const current = snapshot.current;\n      const previous = snapshot.previous;\n      expect(current.has(ComponentA)).toBeFalsy();\n      expect(current.get(ComponentA)).toBeUndefined();\n      expect(previous.has(ComponentA)).toBeTruthy();\n      expect(previous.get(ComponentA)).toBeDefined();\n    }\n    {\n      entity.remove(TAG_C);\n      expect(snapshot.current.has(TAG_C)).toBeFalsy();\n      expect(snapshot.previous.has(TAG_C)).toBeTruthy();\n    }\n  });\n\n  it('Adding linked component must replace all existing linked component instances', () => {\n    const entity = new Entity()\n      .append(new Damage(1))\n      .append(new Damage(2))\n      .append(new Damage(3));\n\n    entity.add(new Damage(100));\n    expect(entity.lengthOf(Damage)).toBe(1);\n  });\n\n  it('Replacing linked component with \"add\" must trigger onComponentRemoved for every appended linked component', () => {\n    const entity = new Entity()\n      .append(new Damage(1))\n      .append(new Damage(2))\n      .append(new Damage(3));\n\n    let removedNumber = 0;\n    entity.onComponentRemoved.connect(() => {\n      removedNumber++;\n    });\n    entity.add(new Damage(100));\n    expect(removedNumber).toBe(3);\n  });\n});\n"
  },
  {
    "path": "tests/unit/linked.list.spec.ts",
    "content": "import {LinkedComponentList} from '../../src/ecs/LinkedComponentList';\nimport {LinkedComponent} from '../../src';\n\nclass Component extends LinkedComponent {}\n\ndescribe('Linked list', () => {\n  it(`Adding component to the empty list putting it to the head`, () => {\n    const list = new LinkedComponentList();\n    const component = new Component();\n    list.add(component);\n    expect(list.head).toBe(component);\n  });\n\n  it(`Adding component to the empty list makes it non-empty`, () => {\n    const list = new LinkedComponentList();\n    const component = new Component();\n    expect(list.isEmpty).toBeTruthy();\n    list.add(component);\n    expect(list.isEmpty).toBeFalsy();\n  });\n\n  it(`Removing component from the list makes it empty`, () => {\n    const list = new LinkedComponentList();\n    const component = new Component();\n    list.add(component);\n    expect(list.remove(component)).toBeTruthy();\n    expect(list.isEmpty).toBeTruthy();\n  });\n\n  it(`Removing component from the the empty list returns false`, () => {\n    const list = new LinkedComponentList();\n    const component = new Component();\n    expect(list.remove(component)).toBeFalsy();\n  });\n\n  it(`Removing not head component from the the list not makes it empty`, () => {\n    const list = new LinkedComponentList();\n    const component1 = new Component();\n    const component2 = new Component();\n    list.add(component1);\n    list.add(component2);\n    list.remove(component2);\n    expect(list.isEmpty).toBeFalsy();\n  });\n\n  it(`\"iterate\" iterates through all components in the list`, () => {\n    const list = new LinkedComponentList();\n    const components = [new Component(), new Component(), new Component()];\n    components.forEach((component) => list.add(component));\n    list.iterate((component) => {\n      const index = components.indexOf(component);\n      expect(index).not.toBe(-1);\n      components.splice(index, 1);\n    });\n    expect(components.length).toBe(0);\n  });\n\n  it('removing current component during iteration won\\'t breaks iteration', () => {\n    const list = new LinkedComponentList();\n    const components = [new Component(), new Component(), new Component()];\n    components.forEach((component) => list.add(component));\n    list.iterate((component) => {\n      list.remove(component);\n      const index = components.indexOf(component);\n      expect(index).not.toBe(-1);\n      components.splice(index, 1);\n    });\n    expect(components.length).toBe(0);\n  });\n\n  it(`\"clear\" removes all components from the list`, () => {\n    const list = new LinkedComponentList();\n    list.add(new Component());\n    list.add(new Component());\n    list.add(new Component());\n    list.clear();\n    expect(list.isEmpty).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "tests/unit/query.spec.ts",
    "content": "import {Engine, Entity, LinkedComponent, Query, QueryBuilder} from '../../src';\n\nclass Position {\n  public x: number = 0;\n  public y: number = 0;\n\n  constructor(x: number = 0, y: number = 0) {\n    this.x = x;\n    this.y = y;\n  }\n}\n\nclass View {}\n\nclass Move {}\n\nclass Stay {}\n\nclass Damage extends LinkedComponent {}\n\ndescribe('Query builder', () => {\n  it('Building query', () => {\n    const query = new QueryBuilder()\n      .contains(Position)\n      .contains(View)\n      .build();\n    expect(query).toBeDefined();\n    expect(query.entities).toBeDefined();\n    expect(query.isEmpty).toBeTruthy();\n  });\n\n  it('Expected that built query matches defined pattern', () => {\n    const query = new QueryBuilder()\n      .contains(Position)\n      .contains(View)\n      .build();\n    const entities = [\n      new Entity().add(new Position()).add(new View()),\n      new Entity().add(new Position()).add(new View()),\n    ];\n    query.matchEntities(entities);\n    expect(query.length).toBe(2);\n  });\n\n  it(`Expected that adding the same component to the builder twice will use only it only once for construction of predicate `, () => {\n    const builder = new QueryBuilder()\n      .contains(Position)\n      .contains(Position)\n      .contains(View);\n    expect(builder.getComponents().size).toBe(2);\n  });\n\n  it(`Expected that adding the same tag to the builder twice will use only it only once for construction of predicate `, () => {\n    const TAG = 1;\n    const builder = new QueryBuilder()\n      .contains(TAG)\n      .contains(TAG);\n    expect(builder.getTags().size).toBe(1);\n  });\n\n  it(`Expected that query built with QueryBuilder matches entities with provided conditions`, () => {\n    const TAG = 1;\n    const query = new QueryBuilder().contains(Position, TAG).build();\n    query.matchEntities([\n      new Entity().add(new Position()).add(TAG),\n      new Entity(),\n      new Entity().add(new Position()),\n      new Entity().add(TAG),\n    ]);\n    expect(query.length).toBe(1);\n  });\n\n  it(`Expected that query built with QueryBuilder matches entities with provided conditions (no components)`, () => {\n    const TAG = 1;\n    const query = new QueryBuilder().contains(TAG).build();\n    query.matchEntities([\n      new Entity().add(new Position()).add(TAG),\n      new Entity(),\n      new Entity().add(new Position()),\n      new Entity().add(TAG),\n    ]);\n    expect(query.length).toBe(2);\n  });\n\n  it(`Expected that query built with QueryBuilder matches entities with provided conditions (no tags)`, () => {\n    const TAG = 1;\n    const query = new QueryBuilder().contains(Position).build();\n    query.matchEntities([\n      new Entity().add(new Position()).add(TAG),\n      new Entity(),\n      new Entity().add(new Position()),\n      new Entity().add(TAG),\n    ]);\n    expect(query.length).toBe(2);\n  });\n});\n\ndescribe('Query matching', () => {\n  const position = new Position();\n  const view = new View();\n  const move = new Move();\n  const stay = new Stay();\n\n  function getQuery() {\n    return new QueryBuilder()\n      .contains(Position, View)\n      .build();\n  }\n\n  it('Query not matching entity with only position component', () => {\n    const engine = new Engine();\n    const entity = new Entity().add(position);\n    const query = getQuery();\n    engine.addQuery(query);\n    engine.addEntity(entity);\n    expect(query.entities).toBeDefined();\n    expect(query.isEmpty).toBeTruthy();\n  });\n\n  it('Query not matching entity with only view component', () => {\n    const engine = new Engine();\n    const entity = new Entity().add(view);\n    const query = getQuery();\n    engine.addQuery(query);\n    engine.addEntity(entity);\n\n    expect(query.entities).toBeDefined();\n    expect(query.isEmpty).toBeTruthy();\n  });\n\n  it('Query matching entity with view and position components', () => {\n    const engine = new Engine();\n    const entity = new Entity().add(position).add(view);\n    const query = getQuery();\n    engine.addQuery(query);\n    engine.addEntity(entity);\n\n    expect(query.entities).toBeDefined();\n    expect(query.isEmpty).toBeFalsy();\n    expect(query.entities[0]).toBe(entity);\n  });\n\n  it(`Expected that 'has' returns true for entity that is in the query`, () => {\n    const targetEntity = new Entity().add(view);\n    const entities = [\n      new Entity().add(position),\n      targetEntity,\n      new Entity().add(view).add(position),\n    ];\n    const query = new Query((entity) => entity.has(View));\n    query.matchEntities(entities);\n    expect(query.has(targetEntity)).toBeTruthy();\n  });\n\n  it('Adding component to entity adding it to query', () => {\n    const engine = new Engine();\n    const entity = new Entity().add(position);\n    const query = getQuery();\n    engine.addQuery(query);\n    engine.addEntity(entity);\n\n    expect(query.entities).toBeDefined();\n    expect(query.isEmpty).toBeTruthy();\n\n    entity.add(view);\n\n    expect(query.entities.length).toBe(1);\n  });\n\n  it('Removing component removes entity from query', () => {\n    const engine = new Engine();\n    const entity = new Entity().add(position).add(view);\n    const query = getQuery();\n    engine.addQuery(query);\n    engine.addEntity(entity);\n\n    expect(query.entities).toBeDefined();\n    expect(query.entities.length).toBe(1);\n    expect(query.entities[0]).toBe(entity);\n\n    entity.remove(View);\n\n    expect(query.isEmpty).toBeTruthy();\n  });\n\n  it('Removing not matching with query components not removes entity from query', () => {\n    const engine = new Engine();\n    const entity = new Entity()\n      .add(position)\n      .add(view)\n      .add(move);\n\n    const query = getQuery();\n    engine.addQuery(query);\n    engine.addEntity(entity);\n\n    expect(query.entities).toBeDefined();\n    expect(query.entities.length).toBe(1);\n    expect(query.entities[0]).toBe(entity);\n\n    entity.remove(Move);\n\n    expect(query.entities.length).toBe(1);\n    expect(query.entities[0]).toBe(entity);\n\n    entity.add(stay);\n\n    expect(query.entities.length).toBe(1);\n    expect(query.entities[0]).toBe(entity);\n\n    entity.remove(View);\n\n    expect(query.isEmpty).toBeTruthy();\n  });\n\n  it('Removing entity from engine removes entity from query', () => {\n    const engine = new Engine();\n    const entity = new Entity().add(position).add(view);\n    const query = getQuery();\n    engine.addQuery(query);\n    engine.addEntity(entity);\n\n    expect(query.entities).toBeDefined();\n    expect(query.entities.length).toBe(1);\n    expect(query.entities[0]).toBe(entity);\n\n    engine.removeEntity(entity);\n\n    expect(query.isEmpty).toBeTruthy();\n  });\n\n  it('Removing query from engine clears query and not updating it anymore', () => {\n    const engine = new Engine();\n    const entity = new Entity().add(position).add(view);\n    const query = getQuery();\n    engine.addQuery(query);\n    engine.addEntity(entity);\n\n    expect(query.entities).toBeDefined();\n    expect(query.entities.length).toBe(1);\n    expect(query.entities[0]).toBe(entity);\n\n    engine.removeQuery(query);\n    expect(query.isEmpty).toBeTruthy();\n\n    engine.removeEntity(entity);\n    engine.addEntity(entity);\n\n    expect(query.isEmpty).toBeTruthy();\n  });\n\n  it('Entity invalidation should add entity to query with custom predicate', () => {\n    const engine = new Engine();\n    const entity = new Entity().add(new Position(0, 0));\n    const query = new Query((entity: Entity) => {\n      return entity.has(Position) && entity.get(Position)!.y > 100;\n    });\n    engine.addQuery(query);\n    engine.addEntity(entity);\n\n    expect(query.entities.length).toBe(0);\n    entity.get(Position)!.y = 150;\n    entity.invalidate();\n    expect(query.entities.length).toBe(1);\n  });\n\n  it('Entity invalidation should remove entity from query with custom predicate', () => {\n    const engine = new Engine();\n    const entity = new Entity().add(new Position(0, 0));\n    const query = new Query((entity: Entity) => {\n      return entity.has(Position) && entity.get(Position)!.y === 0;\n    });\n    engine.addQuery(query);\n    engine.addEntity(entity);\n\n    expect(query.entities.length).toBe(1);\n    entity.get(Position)!.y = 150;\n    entity.invalidate();\n    expect(query.entities.length).toBe(0);\n  });\n\n  it('Entity invalidation should add entity to query with custom predicate', () => {\n    const engine = new Engine();\n    const entity = new Entity().add(new Position(0, 150));\n    const query = new Query((entity: Entity) => {\n      return entity.has(Position) && entity.get(Position)!.y === 0;\n    });\n    engine.addQuery(query);\n    engine.addEntity(entity);\n\n    expect(query.entities.length).toBe(0);\n    entity.get(Position)!.y = 0;\n    entity.invalidate();\n    expect(query.entities.length).toBe(1);\n  });\n\n  it('Removing and adding components to entity should properly update custom query', () => {\n    const engine = new Engine();\n    const entity = new Entity().add(new Position(0, 0));\n    const query = new Query((entity: Entity) => {\n      return entity.has(Position) && !entity.has(View);\n    });\n    engine.addQuery(query);\n    engine.addEntity(entity);\n\n    expect(query.length).toBe(1);\n    entity.add(new View());\n    expect(query.length).toBe(0);\n    entity.remove(View);\n    expect(query.length).toBe(1);\n  });\n\n  it('Adding and removing entity that not related to query, must not affect it', () => {\n    const engine = new Engine();\n    const entity1 = new Entity().add(new Position(0, 0));\n    const entity2 = new Entity();\n    const query = new Query((entity: Entity) => {\n      return entity.has(Position);\n    });\n    engine.addQuery(query);\n    engine.addEntity(entity1);\n    expect(query.length).toBe(1);\n    engine.addEntity(entity2);\n    expect(query.length).toBe(1);\n    engine.removeEntity(entity2);\n    expect(query.length).toBe(1);\n  });\n\n  it(`countBy returns the number of elements that tested by predicate successfully`, () => {\n    const initialEntitiesAmount = 10;\n    const entitiesWithViewAmount = 4;\n\n    const query = new Query((entity: Entity) => {\n      return entity.has(Position);\n    });\n\n    const entities = [];\n    for (let i = 0; i < initialEntitiesAmount; i++) {\n      const entity = new Entity().add(new Position());\n      if (i < entitiesWithViewAmount) {\n        entity.add(new View());\n      }\n      entities.push(entity);\n    }\n    query.matchEntities(entities);\n    expect(query.countBy((entity: Entity) => entity.hasAll(View, Position))).toBe(entitiesWithViewAmount);\n  });\n\n  it(`countBy returns zero for empty query`, () => {\n    const query = new Query((entity: Entity) => {\n      return entity.has(Position);\n    });\n    expect(query.countBy((entity: Entity) => entity.hasAll(Position))).toBe(0);\n  });\n\n  it(`'first' getter returns first element from the query`, () => {\n    const query = new Query((entity: Entity) => {\n      return entity.has(Position);\n    });\n    const entities = [new Entity().add(new Position()), new Entity().add(new Position())];\n    const firstElement = entities[0];\n    query.matchEntities(entities);\n    expect(query.first).toBe(firstElement);\n  });\n\n  it(`'first' getter returns undefined if the query is empty`, () => {\n    const query = new Query((entity: Entity) => {\n      return entity.has(Position);\n    });\n    expect(query.first).toBeUndefined();\n  });\n\n  it(`'last' getter returns last element from the query`, () => {\n    const query = new Query((entity: Entity) => {\n      return entity.has(Position);\n    });\n    const entities = [new Entity().add(new Position()), new Entity().add(new Position())];\n    const lastElement = entities[1];\n    query.matchEntities(entities);\n    expect(query.last).toBe(lastElement);\n  });\n\n  it(`'last' getter returns undefined if the query is empty`, () => {\n    const query = new Query((entity: Entity) => {\n      return entity.has(Position);\n    });\n    expect(query.last).toBeUndefined();\n  });\n\n  it(`'find' returns first element that is accepted by predicate`, () => {\n    const query = new Query((entity: Entity) => {\n      return entity.has(Position);\n    });\n    const entities = [\n      new Entity().add(new Position()),\n      new Entity().add(new Position()).add(new View()),\n      new Entity().add(new Position()).add(new View()),\n    ];\n    const targetEntity = entities[1];\n    query.matchEntities(entities);\n    expect(query.find((value) => value.has(View))).toBe(targetEntity);\n  });\n\n  it(`'find' returns undefined when no suitable elements found`, () => {\n    const query = new Query((entity: Entity) => {\n      return entity.has(Position);\n    });\n    query.matchEntities([\n      new Entity().add(new Position()),\n      new Entity().add(new Position()),\n      new Entity().add(new Position()),\n    ]);\n    expect(query.find((value) => value.has(View))).toBeUndefined();\n  });\n\n  it(`'filter' returns all suitable elements`, () => {\n    const query = new Query((entity: Entity) => {\n      return entity.has(Position);\n    });\n    const TAG = 'tag';\n    const entities = [\n      new Entity().add(new Position()),\n      new Entity().add(new Position()).add(TAG),\n      new Entity().add(new Position()).add(TAG),\n    ];\n    query.matchEntities(entities);\n    const filteredItems = query.filter((value) => value.has(TAG));\n    expect(filteredItems.length).toBe(2);\n    expect(filteredItems[0]).toBe(entities[1]);\n    expect(filteredItems[1]).toBe(entities[2]);\n  });\n\n  it(`'filter' returns empty array when no suitable elements found`, () => {\n    const query = new Query((entity: Entity) => {\n      return entity.has(Position);\n    });\n    const TAG = 'tag';\n    const entities = [\n      new Entity().add(TAG),\n      new Entity().add(TAG),\n      new Entity().add(TAG),\n    ];\n    query.matchEntities(entities);\n    const filteredItems = query.filter((value) => value.has(Position));\n    expect(filteredItems.length).toBe(0);\n  });\n\n  it(`appending LinkedComponent affects query`, () => {\n    class LogComponent extends LinkedComponent {\n      public constructor(public readonly log: string) {\n        super();\n      }\n    }\n\n    const query = new Query((entity: Entity) => {\n      return entity.has(LogComponent);\n    });\n    const entity = new Entity()\n      .append(new LogComponent('test'));\n\n    query.matchEntities([entity]);\n    expect(query.length).toBe(1);\n  });\n});\n\ndescribe('Query signals', () => {\n  it('Query`s onEntityAdded must be invoked when entity is added to query', () => {\n    const query = new Query((entity: Entity) => {\n      return entity.hasAll(View, Position);\n    });\n\n    let currentState = undefined;\n    let previousState = undefined;\n\n    query.onEntityAdded.connect(snapshot => {\n      currentState = snapshot.current.hasAll(View, Position);\n      previousState = snapshot.previous.has(View) && !snapshot.previous.has(Position);\n    });\n\n    const entity = new Entity()\n      .add(new View());\n\n    query.matchEntities([entity]);\n    entity.add(new Position());\n    query.entityComponentAdded(entity, entity.get(Position)!, Position);\n\n    expect(currentState).toBeTruthy();\n    expect(previousState).toBeTruthy();\n  });\n\n  it('Query`s onEntityRemoved must be invoked when entity is removed from query', () => {\n    const query = new Query((entity: Entity) => {\n      return entity.hasAll(View, Position);\n    });\n\n    let currentState = undefined;\n    let previousState = undefined;\n\n    query.onEntityRemoved.connect(snapshot => {\n      currentState = snapshot.current.has(View) && !snapshot.current.has(Position);\n      previousState = snapshot.previous.hasAll(View, Position);\n    });\n\n    const entity = new Entity()\n      .add(new View())\n      .add(new Position());\n\n    query.matchEntities([entity]);\n    query.entityComponentRemoved(entity, entity.remove(Position)!, Position);\n\n    expect(currentState).toBeTruthy();\n    expect(previousState).toBeTruthy();\n  });\n\n  it('Query`s onEntityAdded must be invoked when specific component is removed from entity', () => {\n    const query = new Query((entity: Entity) => {\n      return entity.has(View) && !entity.has(Position);\n    });\n\n    let currentState = undefined;\n    let previousState = undefined;\n\n    query.onEntityAdded.connect(snapshot => {\n      currentState = snapshot.current.has(View) && !snapshot.current.has(Position);\n      previousState = snapshot.previous.hasAll(View, Position);\n    });\n\n    const entity = new Entity()\n      .add(new View())\n      .add(new Position());\n\n    query.matchEntities([entity]);\n    query.entityComponentRemoved(entity, entity.remove(Position)!, Position);\n\n    expect(currentState).toBeTruthy();\n    expect(previousState).toBeTruthy();\n  });\n\n  it('Query`s onEntityRemoved must be invoked when specific component is added to entity', () => {\n    const query = new Query((entity: Entity) => {\n      return entity.has(View) && !entity.has(Position);\n    });\n\n    let currentState = undefined;\n    let previousState = undefined;\n\n    query.onEntityRemoved.connect(snapshot => {\n      currentState = snapshot.current.hasAll(View, Position);\n      previousState = snapshot.previous.has(View) && !snapshot.previous.has(Position);\n    });\n\n    const entity = new Entity()\n      .add(new View());\n\n    query.matchEntities([entity]);\n    entity.add(new Position());\n    query.entityComponentAdded(entity, entity.get(Position)!, Position);\n\n    expect(currentState).toBeTruthy();\n    expect(previousState).toBeTruthy();\n  });\n\n  it('Query onEntityAdded mustn\\'t be triggered more than once if several linked components added to the entity', () => {\n    const query = new Query((entity: Entity) => {\n      return entity.has(Damage);\n    });\n\n    let addedNumber = 0;\n    query.onEntityAdded.connect(snapshot => {\n      addedNumber++;\n    });\n\n    const entity = new Entity()\n      .append(new Damage());\n\n    query.matchEntities([entity]);\n    for (let i = 0; i < 3; i++) {\n      const damage = new Damage();\n      entity.append(damage);\n      query.entityComponentAdded(entity, damage, Damage);\n    }\n\n    expect(addedNumber).toBe(1);\n  });\n\n  it('Query onEntityRemoved must be triggered only when last linked component withdrawn', () => {\n    const query = new Query((entity: Entity) => {\n      return entity.has(Damage);\n    });\n\n    let removedNumber = 0;\n    query.onEntityRemoved.connect(snapshot => {\n      removedNumber++;\n    });\n\n    const entity = new Entity()\n      .append(new Damage())\n      .append(new Damage())\n      .append(new Damage());\n\n    query.matchEntities([entity]);\n    while (entity.has(Damage)) {\n      const damage = entity.withdraw(Damage)!;\n      query.entityComponentRemoved(entity, damage, Damage);\n    }\n\n    expect(removedNumber).toBe(1);\n  });\n\n  it(`Query.entityComponentAdded will be call after all other connected handlers`, () => {\n    const engine = new Engine();\n    const query = new Query((entity) => entity.has(View));\n    const entity = new Entity();\n    engine.addQuery(query);\n    engine.addEntity(entity);\n\n    let callIndex = 0;\n    let queryCallIndex;\n    entity.onComponentAdded.connect(() => {\n      callIndex++;\n    });\n    query.onEntityAdded.connect(() => {\n      queryCallIndex = callIndex++;\n    });\n    entity.add(new View());\n    expect(queryCallIndex).toBe(1);\n  });\n\n  it(`Query.entityComponentRemoved must be called only when all linked components are removed and after all other handlers`, () => {\n    const engine = new Engine();\n    const query = new Query((entity) => entity.has(Damage));\n    const entity = new Entity();\n    engine.addQuery(query);\n    engine.addEntity(entity);\n    entity\n      .append(new Damage())\n      .append(new Damage())\n      .append(new Damage())\n      .append(new Damage())\n      .append(new Damage());\n    let callIndex = 0;\n    let queryCallIndex;\n    entity.onComponentRemoved.connect(() => {\n      callIndex++;\n    }, 1000);\n    query.onEntityRemoved.connect(() => {\n      queryCallIndex = callIndex++;\n    });\n    entity.remove(Damage);\n    expect(queryCallIndex).toBe(5);\n  });\n});\n"
  },
  {
    "path": "tests/unit/shared.config.spec.ts",
    "content": "import {Engine, Query, System} from '../../src';\n\ndescribe('Shared config', () => {\n  it('Shared config is accessible when system added to engine', () => {\n    let sharedConfigAccessible = false;\n\n    const engine = new Engine();\n    const system = new class extends System {\n      public onAddedToEngine() {\n        sharedConfigAccessible = this.sharedConfig !== undefined;\n      }\n    }();\n\n    expect(() => {\n      engine.addSystem(system);\n    }).not.toThrowError();\n    expect(sharedConfigAccessible).toBeTruthy();\n  });\n\n  it('Accessing shared config throws an error, when system is not added to engine', () => {\n    expect(() => {\n      new class extends System {\n        public constructor() {\n          super();\n          this.sharedConfig;\n        }\n      };\n    }).toThrowError();\n  });\n\n  it(`Shared config can't be removed from engine`, () => {\n    expect(() => {\n      class Component {}\n\n      const engine = new Engine();\n      engine.sharedConfig.add(new Component());\n      engine.removeEntity(engine.sharedConfig);\n\n      let sharedConfigAccessibleAndStillTheSame = false;\n      const system = new class extends System {\n        public onAddedToEngine() {\n          sharedConfigAccessibleAndStillTheSame = this.sharedConfig !== undefined && this.sharedConfig.has(Component);\n        }\n      };\n      engine.addSystem(system);\n      expect(sharedConfigAccessibleAndStillTheSame).toBeTruthy();\n    });\n  });\n\n  it(`Shared config is presented in the queries`, () => {\n    expect(() => {\n      const TAG = 'tag';\n      const engine = new Engine();\n      const query = new Query((entity) => entity.has(TAG));\n      engine.sharedConfig.add(TAG);\n      engine.addQuery(query);\n\n      expect(query.length).toBe(1);\n      expect(query.first).toBe(engine.sharedConfig);\n    });\n  });\n});\n"
  },
  {
    "path": "tests/unit/signal.spec.ts",
    "content": "import {Signal} from '../../src/utils/Signal';\n\ndescribe('Signals', function () {\n  it('Connecting increases amount of handlers', () => {\n    const signal = new Signal<(value: number) => void>();\n    signal.connect((value: number) => {});\n    expect(signal.hasHandlers).toBeTruthy();\n    expect(signal.handlersAmount).toEqual(1);\n  });\n\n  it('Connecting same handler twice add it only once', () => {\n    const signal = new Signal<(value: number) => void>();\n    const handler = (value: number) => {};\n    signal.connect(handler);\n    signal.connect(handler);\n    expect(signal.handlersAmount).toBe(1);\n  });\n\n  it('Disconnecting decreases amount of handlers', () => {\n    const signal = new Signal<(value: number) => void>();\n    const handler = (value: number) => {};\n    signal.connect(handler);\n    signal.disconnect(handler);\n    expect(signal.hasHandlers).toBeFalsy();\n  });\n\n  it('Disconnecting not connected handler don\\'t remove any existing handler', () => {\n    const signal = new Signal<(value: number) => void>();\n    const addedHandler = (value: number) => {};\n    const wrongHandler = (value: number) => {};\n    signal.connect(addedHandler);\n    signal.disconnect(wrongHandler);\n    expect(signal.handlersAmount).toEqual(1);\n  });\n\n  it('Disconnecting all handlers clears them from signal', () => {\n    const signal = new Signal<(value: number) => void>();\n    signal.connect(() => {});\n    signal.connect(() => {});\n    signal.connect(() => {});\n    signal.disconnectAll();\n    expect(signal.hasHandlers).toBeFalsy();\n  });\n});\n"
  },
  {
    "path": "tests/unit/system.spec.ts",
    "content": "import {Engine, Entity, EntitySnapshot, IterativeSystem, Query, QueryBuilder, System} from '../../src';\n\nclass Position {\n  public x: number = 0;\n  public y: number = 0;\n\n  public constructor(x: number = 0, y: number = 0) {\n    this.x = x;\n    this.y = y;\n  }\n}\n\nclass MovementSystem extends IterativeSystem {\n  public constructor() {\n    super(new QueryBuilder().contains(Position).build());\n  }\n\n  protected updateEntity(entity: Entity, dt: number): void {\n    const position = entity.get(Position);\n    if (position != null) {\n      position.x += 10 * dt;\n      position.y += 10 * dt;\n    }\n  }\n\n  protected entityAdded = ({current}: EntitySnapshot) => {\n    current.get(Position)!.x = 100;\n  };\n}\n\ndescribe('Iterative system', () => {\n  it('Updating entities', () => {\n    const engine = new Engine();\n    const entity = new Entity().add(new Position());\n\n    engine.addSystem(new MovementSystem());\n    engine.addEntity(entity);\n    engine.update(1);\n\n    const position = entity.get(Position);\n    expect(position).toBeDefined();\n    expect(position!.x).toBe(110);\n    expect(position!.y).toBe(10);\n  });\n\n  it('Entities in prepare should be available', () => {\n    let entities!: ReadonlyArray<Entity>;\n\n    class TestSystem extends IterativeSystem {\n      public constructor() {\n        super(new QueryBuilder().contains(Position).build());\n      }\n\n      protected prepare() {\n        entities = this.entities;\n      }\n\n      protected updateEntity(entity: Entity, dt: number): void {\n      }\n    }\n\n    const engine = new Engine();\n    const entitiesCount = 5;\n    for (let i = 0; i < entitiesCount; i++) {\n      engine.addEntity(new Entity().add(new Position()));\n    }\n    engine.addSystem(new TestSystem());\n\n    expect(entities).toBeDefined();\n    expect(entities.length).toBe(entitiesCount);\n\n  });\n\n  it('Adding and removing should properly construct EntitySnapshot ', () => {\n    let onRemoved: { snapshot?: boolean, entity?: boolean } = {snapshot: undefined, entity: undefined};\n    let onAdded: { snapshot?: boolean, entity?: boolean } = {snapshot: undefined, entity: undefined};\n\n    class MovementSystem extends IterativeSystem {\n      public constructor() {\n        super(new QueryBuilder().contains(Position).build());\n      }\n\n      protected updateEntity(entity: Entity, dt: number): void {\n      }\n\n      protected entityAdded = ({current, previous}: EntitySnapshot) => {\n        onAdded = {snapshot: previous.has(Position), entity: current.has(Position)};\n      };\n\n      protected entityRemoved = ({current, previous}: EntitySnapshot) => {\n        onRemoved = {snapshot: previous.has(Position), entity: current.has(Position)};\n      };\n    }\n\n    const engine = new Engine();\n    const entity = new Entity();\n    const system = new MovementSystem();\n\n    engine.addSystem(system);\n    engine.addEntity(entity);\n    engine.update(1);\n\n    entity.add(new Position());\n    entity.remove(Position);\n\n    expect(onAdded).toEqual({snapshot: false, entity: true});\n    expect(onRemoved).toEqual({snapshot: true, entity: false});\n  });\n\n  it(\"Entities safe removal during iteration should not break the iteration ordering\", () => {\n    class Health {\n      public constructor(public value: number) {\n      }\n    }\n\n    class HealthTickSystem extends IterativeSystem {\n      public constructor() {\n        super(new QueryBuilder().contains(Health).build());\n      }\n\n      protected updateEntity(entity: Entity, dt: number): void {\n        const health = entity.get(Health)!;\n        health.value -= 1;\n        if (health.value <= 0) {\n          this.engine.removeEntity(entity, true);\n        }\n      }\n    }\n\n    const engine = new Engine();\n    engine.addSystem(new HealthTickSystem());\n    for (let i = 0; i < 5; i++) {\n      engine.addEntity(new Entity().add(new Health(1)));\n    }\n    engine.update(1);\n    expect(engine.entities.length).toBe(0);\n  })\n\n  it.each([true, false])(`Re-adding entities which were removed should work after the engine update cycle`, (safe) => {\n    const engine = new Engine();\n    const query = new QueryBuilder().contains(Position).build();\n    engine.addQuery(query);\n\n    for (let i = 0; i < 5; i++) {\n      engine.addEntity(new Entity().add(new Position()));\n    }\n\n    const entities = query.entities.concat()\n    for (let entity of entities) {\n      engine.removeEntity(entity, safe);\n    }\n    for (let entity of entities) {\n      engine.addEntity(entity);\n    }\n    engine.update(0);\n    expect(engine.entities.length).toBe(5);\n  })\n});\n\ndescribe('Failure on accessing engine if not attached to it', () => {\n  it(`Expected that engine can't be accessed if system is not attached to it`, () => {\n    class Message {\n    }\n\n    class TestSystem extends System {\n      public update(dt: number) {\n        this.engine.addEntity(new Entity());\n      }\n    }\n\n    const system = new TestSystem();\n    expect(() => system.update(0)).toThrowError();\n  });\n\n  it(`Expected that message can't be sent if system is not attached to the engine`, () => {\n    class Message {\n    }\n\n    class TestSystem extends System {\n      public update(dt: number) {\n        this.dispatch(new Message());\n      }\n    }\n\n    const system = new TestSystem();\n    expect(() => system.update(0)).toThrowError();\n  });\n\n  it(`Expected that removing system from engine breaking the iteration`, () => {\n    class Component {\n    }\n\n    let amountOfIterations = 0;\n\n    class TestSystem extends IterativeSystem {\n      public constructor() {\n        super(new Query(entity => entity.has(Component)));\n      }\n\n      protected updateEntity(entity: Entity, dt: number) {\n        // In case if iteration continues - after removing system from engine\n        // then the line below should throw an exception\n        this.engine.clear();\n        amountOfIterations++;\n      }\n    }\n\n    const engine = new Engine();\n    engine.addSystem(new TestSystem());\n    engine.addEntity(new Entity().add(new Component()));\n    engine.addEntity(new Entity().add(new Component()));\n    engine.addEntity(new Entity().add(new Component()));\n    expect(() => {\n      engine.update(0);\n    }).not.toThrowError();\n    expect(amountOfIterations).toBe(1);\n  });\n\n  it(`Iterative system should iterate over entities after removing and subsequent adding it to the engine`, () => {\n    class Component {\n    }\n\n    const engine = new Engine();\n    const entity = new Entity().add(new Component());\n    let iterationsCount = 0;\n    const system = new class extends IterativeSystem {\n      public constructor() {\n        super((entity) => entity.has(Component));\n      }\n\n      protected updateEntity(entity: Entity, dt: number) {\n        iterationsCount++;\n      }\n    }();\n    engine.addEntity(entity);\n\n    engine.addSystem(system);\n    engine.update(1);\n\n    engine.removeSystem(system);\n    engine.update(1);\n\n    engine.addSystem(system);\n    engine.update(1);\n\n    expect(iterationsCount).toBe(2);\n  });\n\n  it(`After removal request system must be deleted`, () => {\n    const engine = new Engine();\n    let iterationsCount = 0;\n    const system = new class extends System {\n      public update(dt: number) {\n        iterationsCount++;\n        this.requestRemoval();\n      }\n    };\n    engine.addSystem(system);\n    for (let i = 0; i < 5; i++) {\n      engine.update(0);\n    }\n    expect(iterationsCount).toBe(1);\n  });\n});"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"moduleResolution\": \"node\",\n    \"module\": \"commonjs\",\n    \"target\": \"es2016\",\n    \"lib\": [\n      \"es2016\"\n    ],\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"declaration\": true,\n    \"noEmitHelpers\": true,\n    \"noImplicitThis\": true,\n    \"alwaysStrict\": true,\n    \"strictNullChecks\": true,\n    \"strictFunctionTypes\": true,\n    \"strictPropertyInitialization\": true,\n    \"importHelpers\": true,\n    \"downlevelIteration\": false,\n    \"experimentalDecorators\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"outDir\": \"lib\",\n    \"typeRoots\": [\"node_modules/@types\"],\n    \"stripInternal\": true\n  },\n  \"include\": [\n    \"src/**/*\"\n  ],\n  \"exclude\": [\n    \"tests/**/*\"\n  ]\n}\n"
  }
]