From 25d52851c1c4bf5b7cf701afb248b1bb6785f17d Mon Sep 17 00:00:00 2001 From: sz-piotr Date: Mon, 14 Aug 2017 13:15:21 +0200 Subject: [PATCH] component now accepts argument names, previous version is now named complexComponent --- examples/basic/index.html | 18 +++------------ lib/ouyo.js | 2 +- lib/ouyo.js.map | 2 +- package.json | 2 +- src/component.js | 17 +++++++++++++- src/component.test.js | 47 ++++++++++++++++++++++++++++++--------- src/entity.test.js | 6 ++--- src/index.js | 2 +- src/query.test.js | 6 ++--- 9 files changed, 66 insertions(+), 36 deletions(-) diff --git a/examples/basic/index.html b/examples/basic/index.html index 179ea16..5a70a3a 100644 --- a/examples/basic/index.html +++ b/examples/basic/index.html @@ -25,21 +25,9 @@ const engine = new Engine() - const PositionComponent = component(function(x, y) { - this.x = x - this.y = y - }) - - const VelocityComponent = component(function(x, y) { - this.x = x - this.y = y - }) - - const RectangleComponent = component(function(width, height, color) { - this.width = width - this.height = height - this.color = color - }) + const PositionComponent = component('x', 'y') + const VelocityComponent = component('x', 'y') + const RectangleComponent = component('width', 'height', 'color') const MovementSystem = { query: Query.all(PositionComponent, VelocityComponent), diff --git a/lib/ouyo.js b/lib/ouyo.js index fbd437a..414a459 100644 --- a/lib/ouyo.js +++ b/lib/ouyo.js @@ -1,2 +1,2 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("OUYO",[],t):"object"==typeof exports?exports.OUYO=t():e.OUYO=t()}(this,function(){return function(e){function t(i){if(n[i])return n[i].exports;var r=n[i]={i:i,l:!1,exports:{}};return e[i].call(r.exports,r,r.exports,t),r.l=!0,r.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,i){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:i})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=1)}([function(e,t,n){"use strict";function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}n.d(t,"a",function(){return u});for(var r=(function(){function e(e,t){for(var n=0;n=o;)n++,e-=o;var i=0!=(this.values[n]&s[e]);t&&!i?this.values[n]+=s[e]:!t&&i&&(this.values[n]-=s[e])}},{key:"matches",value:function(e){for(var t=0;t=this.componentIdMap.length;)this.componentIdMap.push(null);null===this.componentIdMap[e.id]&&(this.componentIdMap[e.id]=this.componentsCount++)}},{key:"start",value:function(e){var t=this;this.started=!0,this.running=!0,e&&e(this);var n=void 0,i=Date.now(),r=function e(){t.running&&(n=Date.now(),t.update((n-i)/1e3),i=n),requestAnimationFrame(e)};requestAnimationFrame(r)}},{key:"update",value:function(e){for(var t=0;t=o;)n++,e-=o;var i=0!=(this.values[n]&s[e]);t&&!i?this.values[n]+=s[e]:!t&&i&&(this.values[n]-=s[e])}},{key:"matches",value:function(e){for(var t=0;t=this.componentIdMap.length;)this.componentIdMap.push(null);null===this.componentIdMap[e.id]&&(this.componentIdMap[e.id]=this.componentsCount++)}},{key:"start",value:function(e){var t=this;this.started=!0,this.running=!0,e&&e(this);var n=void 0,i=Date.now(),r=function e(){t.running&&(n=Date.now(),t.update((n-i)/1e3),i=n),requestAnimationFrame(e)};requestAnimationFrame(r)}},{key:"update",value:function(e){for(var t=0;t= KEY_BITS) {\n valueIndex++;\n index -= KEY_BITS;\n }\n\n var previousValue = (this.values[valueIndex] & POWERS[index]) !== 0;\n if (value && !previousValue) {\n this.values[valueIndex] += POWERS[index];\n } else if (!value && previousValue) {\n this.values[valueIndex] -= POWERS[index];\n }\n }\n }, {\n key: \"matches\",\n value: function matches(other) {\n for (var _i2 = 0; _i2 < other.values.length; _i2++) {\n var currentValue = this.values[_i2] || 0;\n var otherValue = other.values[_i2];\n if ((currentValue & otherValue) !== otherValue) {\n return false;\n }\n }\n return true;\n }\n }]);\n\n return Key;\n}();\n\n/***/ }),\n/* 1 */\n/***/ (function(module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\nObject.defineProperty(__webpack_exports__, \"__esModule\", { value: true });\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__engine__ = __webpack_require__(2);\n/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, \"Engine\", function() { return __WEBPACK_IMPORTED_MODULE_0__engine__[\"a\"]; });\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__component__ = __webpack_require__(4);\n/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, \"component\", function() { return __WEBPACK_IMPORTED_MODULE_1__component__[\"a\"]; });\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__query__ = __webpack_require__(5);\n/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, \"Query\", function() { return __WEBPACK_IMPORTED_MODULE_2__query__[\"a\"]; });\n\n\n\n\n/***/ }),\n/* 2 */\n/***/ (function(module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"a\", function() { return Engine; });\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__entity__ = __webpack_require__(3);\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\n\n\nvar Engine = function () {\n function Engine() {\n _classCallCheck(this, Engine);\n\n this.entities = null;\n this.changed = [];\n this.removed = [];\n\n this.systems = [];\n this.queries = [];\n this.componentIdMap = [];\n this.componentsCount = 0;\n\n this.started = false;\n this.running = false;\n\n this.onEntityKeyChange = this.onEntityKeyChange.bind(this);\n }\n\n _createClass(Engine, [{\n key: 'createEntity',\n value: function createEntity() {\n if (!this.started) {\n throw new Error('Entities cannot be added before the engine is started.');\n }\n\n var entity = new __WEBPACK_IMPORTED_MODULE_0__entity__[\"a\" /* Entity */](this.componentsCount, this.componentIdMap, this.onEntityKeyChange);\n entity.next = this.entities;\n if (this.entities !== null) {\n this.entities.prev = entity;\n }\n this.entities = entity;\n\n return entity;\n }\n }, {\n key: 'removeEntity',\n value: function removeEntity(entity) {\n if (!this.started) {\n throw new Error('Entities cannot be removed before the engine is started.');\n }\n\n var next = entity.next;\n var prev = entity.prev;\n\n if (next !== null) {\n next.prev = entity.prev;\n }\n if (prev !== null) {\n prev.next = entity.next;\n }\n\n entity.next = null;\n entity.prev = null;\n\n if (this.entities = entity) {\n this.entities = next;\n }\n\n this.removed.push(entity);\n }\n }, {\n key: 'onEntityKeyChange',\n value: function onEntityKeyChange(entity) {\n this.changed.push(entity);\n }\n }, {\n key: 'registerSystem',\n value: function registerSystem(_ref) {\n var query = _ref.query,\n sort = _ref.sort,\n update = _ref.update,\n processEntity = _ref.processEntity;\n\n if (this.started) {\n throw new Error('Cannot register system after entities have been added or the engine was started.');\n }\n\n if (bothOrNone(update, processEntity)) {\n throw new Error('Exactly one of \"update\" and \"processEntity\" must be defined on a system.');\n }\n\n if (!query && processEntity) {\n throw new Error('\"processEntity\" can only be used with a query.');\n }\n\n if (!query && sort) {\n throw new Error('\"sort\" can only be used with a query.');\n }\n\n var system = {\n query: query ? query.bake(this) : false,\n sort: sort,\n sorted: [],\n update: update || function (entities, timeDelta, engine) {\n for (var i = 0; i < entities.length; ++i) {\n processEntity(entities[i], timeDelta, engine);\n }\n }\n };\n\n this.systems.push(system);\n\n if (system.query) {\n this.queries.push(system.query);\n }\n }\n }, {\n key: 'registerComponent',\n value: function registerComponent(component) {\n if (this.started) {\n throw new Error('Cannot register component after entities have been added or the engine was started.');\n }\n\n while (component.id >= this.componentIdMap.length) {\n this.componentIdMap.push(null);\n }\n var index = this.componentIdMap[component.id];\n if (index === null) {\n this.componentIdMap[component.id] = this.componentsCount++;\n }\n }\n }, {\n key: 'start',\n value: function start(init) {\n var _this = this;\n\n // TODO: add pause\n this.started = true;\n this.running = true;\n if (init) {\n init(this);\n }\n\n var now = void 0;\n var lastTime = Date.now();\n var tick = function tick() {\n if (_this.running) {\n now = Date.now();\n _this.update((now - lastTime) / 1000);\n lastTime = now;\n }\n requestAnimationFrame(tick);\n };\n requestAnimationFrame(tick);\n }\n }, {\n key: 'update',\n value: function update(timeDelta) {\n for (var i = 0; i < this.systems.length; ++i) {\n this.runSystem(this.systems[i], timeDelta);\n this.handleChanges();\n }\n }\n }, {\n key: 'runSystem',\n value: function runSystem(system, timeDelta) {\n if (system.query) {\n var entities = system.query.entities;\n if (system.sort) {\n copyArray(entities, system.sorted);\n entities = system.sorted;\n entities.sort(system.sort);\n }\n system.update(entities, timeDelta, this);\n } else {\n system.update(timeDelta, this);\n }\n }\n }, {\n key: 'handleChanges',\n value: function handleChanges() {\n for (var i = 0; i < this.changed.length; i++) {\n var changed = this.changed[i];\n for (var j = 0; j < this.queries.length; j++) {\n this.queries[j].onChange(changed);\n }\n }\n this.changed.length = 0;\n\n for (var _i = 0; _i < this.removed.length; _i++) {\n var removed = this.removed[_i];\n for (var _j = 0; _j < this.queries.length; _j++) {\n this.queries[_j].onRemove(removed);\n }\n }\n this.changed.length = 0;\n }\n }]);\n\n return Engine;\n}();\n\nfunction bothOrNone(a, b) {\n return a && b || !a && !b;\n}\n\nfunction copyArray(from, to) {\n for (var i = 0; i < from.length; ++i) {\n to[i] = from[i];\n }\n to.length = from.length;\n}\n\n/***/ }),\n/* 3 */\n/***/ (function(module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"a\", function() { return Entity; });\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__key__ = __webpack_require__(0);\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\n\n\nvar index = 0;\n\nvar Entity = function () {\n function Entity(componentCount, componentIdMap, onKeyChanged) {\n _classCallCheck(this, Entity);\n\n this.id = index++;\n this.components = [];\n for (var i = 0; i < componentCount; i++) {\n this.components.push(null);\n }\n this.componentIdMap = componentIdMap;\n this.key = new __WEBPACK_IMPORTED_MODULE_0__key__[\"a\" /* Key */](componentCount);\n\n this.prev = null;\n this.next = null;\n\n this.keyChangeAnnounced = false;\n this.onKeyChanged = onKeyChanged;\n }\n\n _createClass(Entity, [{\n key: 'add',\n value: function add(component) {\n var index = this.getIndexById(component._id);\n if (this.components[index]) {\n throw new Error('Cannot add another instance of the same component.');\n }\n this.key.setBit(index, true);\n this.components[index] = component;\n this.onChange();\n return this;\n }\n }, {\n key: 'has',\n value: function has(Component) {\n var index = this.getIndexById(Component.id);\n return this.components[index] ? true : false;\n }\n }, {\n key: 'get',\n value: function get(Component) {\n var index = this.getIndexById(Component.id);\n var component = this.components[index];\n if (!component) {\n throw new Error('Requested component is not present.');\n }\n return component;\n }\n }, {\n key: 'remove',\n value: function remove(Component) {\n var index = this.getIndexById(Component.id);\n var component = this.components[index];\n if (!component) {\n throw new Error('Cannot remove component instance, because it doesn\\'t exist on target entity.');\n }\n Component.destroy(component);\n this.key.setBit(index, false);\n this.components[index] = null;\n this.onChange();\n return this;\n }\n }, {\n key: 'onChange',\n value: function onChange() {\n if (!this.keyChangeAnnounced) {\n this.onKeyChanged(this);\n }\n this.keyChangeAnnounced = true;\n }\n }, {\n key: 'resetOnKeyChanged',\n value: function resetOnKeyChanged() {\n this.keyChangeAnnounced = false;\n }\n }, {\n key: 'getIndexById',\n value: function getIndexById(componentId) {\n var index = this.componentIdMap[componentId];\n if (index === undefined) {\n throw new Error('Unknown component passed as argument. Components not included in queries should be manually' + ' registered using Engine.registerComponent().');\n }\n return index;\n }\n }]);\n\n return Entity;\n}();\n\n/***/ }),\n/* 4 */\n/***/ (function(module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n/* harmony export (immutable) */ __webpack_exports__[\"a\"] = component;\nvar index = 0;\n\nfunction component(objectConstructor, objectDestructor) {\n var id = index++;\n objectConstructor.id = id;\n objectConstructor.prototype._id = id;\n\n objectConstructor.destroy = function (instance) {\n if (objectDestructor) {\n objectDestructor.apply(instance);\n }\n };\n\n return objectConstructor;\n}\n\n/***/ }),\n/* 5 */\n/***/ (function(module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"a\", function() { return Query; });\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__key__ = __webpack_require__(0);\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\n\n\nvar Query = {\n all: function all() {\n var _ref;\n\n return (_ref = new UnbakedQuery()).all.apply(_ref, arguments);\n }\n};\n\nvar UnbakedQuery = function () {\n function UnbakedQuery() {\n _classCallCheck(this, UnbakedQuery);\n\n this.components = [];\n }\n\n _createClass(UnbakedQuery, [{\n key: 'all',\n value: function all() {\n var _components;\n\n if (arguments.length === 0) {\n throw new Error('The all() method must take at least one argument.');\n }\n (_components = this.components).push.apply(_components, arguments);\n\n return this;\n }\n }, {\n key: 'bake',\n value: function bake(engine) {\n return new BakedQuery(this, engine);\n }\n }]);\n\n return UnbakedQuery;\n}();\n\nvar BakedQuery = function () {\n function BakedQuery(_ref2, engine) {\n var _this = this;\n\n var components = _ref2.components;\n\n _classCallCheck(this, BakedQuery);\n\n this.key = new __WEBPACK_IMPORTED_MODULE_0__key__[\"a\" /* Key */](engine.componentsCount + components.length);\n components.forEach(function (component) {\n engine.registerComponent(component);\n _this.key.set(engine.componentIdMap[component.id]);\n });\n this.idMap = {};\n this.entities = [];\n }\n\n _createClass(BakedQuery, [{\n key: 'onChange',\n value: function onChange(entity) {\n var index = this.idMap[entity.id];\n var matched = entity.key.matches(this.key);\n if (index === undefined && matched) {\n this.idMap[entity.id] = this.entities.length;\n this.entities.push(entity);\n } else if (index !== undefined && !matched) {\n this.remove(entity, index);\n }\n }\n }, {\n key: 'onRemove',\n value: function onRemove(entity) {\n var index = this.idMap[entity.id];\n if (index !== undefined) {\n this.remove(entity, index);\n }\n }\n }, {\n key: 'remove',\n value: function remove(entity, index) {\n delete this.idMap[entity.id];\n var otherEntity = this.entities.pop();\n if (otherEntity !== entity) {\n this.entities[index] = otherEntity;\n this.idMap[otherEntity.id] = index;\n }\n }\n }]);\n\n return BakedQuery;\n}();\n\n/***/ })\n/******/ ]);\n});\n\n\n// WEBPACK FOOTER //\n// ouyo.js"," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 1);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 8b29c3bdc55fba3e7f6f","const KEY_BITS = 31\r\n\r\nconst POWERS = []\r\nfor(let i = 0; i < KEY_BITS; i++) {\r\n POWERS[i] = 2 ** i\r\n}\r\n\r\nexport class Key {\r\n constructor(length) {\r\n this.values = []\r\n for(let i = 0; i < length / KEY_BITS; i++) {\r\n this.values.push(0)\r\n }\r\n }\r\n\r\n set(index) {\r\n this.setBit(index, true)\r\n return this\r\n }\r\n\r\n unset(index) {\r\n this.setBit(index, false)\r\n return this\r\n }\r\n\r\n setBit(index, value) {\r\n let valueIndex = 0\r\n while(index >= KEY_BITS) {\r\n valueIndex++\r\n index -= KEY_BITS\r\n }\r\n\r\n const previousValue = (this.values[valueIndex] & POWERS[index]) !== 0\r\n if(value && !previousValue) {\r\n this.values[valueIndex] += POWERS[index]\r\n } else if(!value && previousValue) {\r\n this.values[valueIndex] -= POWERS[index]\r\n }\r\n }\r\n\r\n matches(other) {\r\n for(let i = 0; i < other.values.length; i++) {\r\n const currentValue = this.values[i] || 0\r\n const otherValue = other.values[i]\r\n if((currentValue & otherValue) !== otherValue) {\r\n return false\r\n }\r\n }\r\n return true\r\n }\r\n}\r\n\n\n\n// WEBPACK FOOTER //\n// ./src/key.js","import { Entity } from './entity'\n\nexport class Engine {\n constructor() {\n this.entities = null\n this.changed = []\n this.removed = []\n\n this.systems = []\n this.queries = []\n this.componentIdMap = []\n this.componentsCount = 0\n\n this.started = false\n this.running = false\n\n this.onEntityKeyChange = this.onEntityKeyChange.bind(this)\n }\n\n createEntity() {\n if(!this.started) {\n throw new Error('Entities cannot be added before the engine is started.')\n }\n\n const entity = new Entity(\n this.componentsCount,\n this.componentIdMap,\n this.onEntityKeyChange\n )\n entity.next = this.entities\n if(this.entities !== null) {\n this.entities.prev = entity\n }\n this.entities = entity\n\n return entity\n }\n\n removeEntity(entity) {\n if(!this.started) {\n throw new Error('Entities cannot be removed before the engine is started.')\n }\n\n const next = entity.next\n const prev = entity.prev\n\n if(next !== null) {\n next.prev = entity.prev\n }\n if(prev !== null) {\n prev.next = entity.next\n }\n\n entity.next = null\n entity.prev = null\n\n if(this.entities = entity) {\n this.entities = next\n }\n\n this.removed.push(entity)\n }\n\n onEntityKeyChange(entity) {\n this.changed.push(entity)\n }\n\n registerSystem({ query, sort, update, processEntity }) {\n if(this.started) {\n throw new Error('Cannot register system after entities have been added or the engine was started.')\n }\n\n if(bothOrNone(update, processEntity)) {\n throw new Error('Exactly one of \"update\" and \"processEntity\" must be defined on a system.')\n }\n\n if(!query && processEntity) {\n throw new Error('\"processEntity\" can only be used with a query.')\n }\n\n if(!query && sort) {\n throw new Error('\"sort\" can only be used with a query.')\n }\n\n const system = {\n query: query ? query.bake(this) : false,\n sort,\n sorted: [],\n update: update || function(entities, timeDelta, engine) {\n for(let i = 0; i < entities.length; ++i) {\n processEntity(entities[i], timeDelta, engine)\n }\n }\n }\n\n this.systems.push(system)\n\n if(system.query) {\n this.queries.push(system.query)\n }\n }\n\n registerComponent(component) {\n if(this.started) {\n throw new Error('Cannot register component after entities have been added or the engine was started.')\n }\n\n while(component.id >= this.componentIdMap.length) {\n this.componentIdMap.push(null)\n }\n const index = this.componentIdMap[component.id]\n if(index === null) {\n this.componentIdMap[component.id] = this.componentsCount++\n }\n }\n\n start(init) {\n // TODO: add pause\n this.started = true\n this.running = true\n if(init) {\n init(this)\n }\n\n let now\n let lastTime = Date.now()\n const tick = () => {\n if(this.running) {\n now = Date.now()\n this.update((now - lastTime) / 1000)\n lastTime = now\n }\n requestAnimationFrame(tick)\n }\n requestAnimationFrame(tick)\n }\n\n update(timeDelta) {\n for(let i = 0; i < this.systems.length; ++i) {\n this.runSystem(this.systems[i], timeDelta)\n this.handleChanges()\n }\n }\n\n runSystem(system, timeDelta) {\n if(system.query) {\n let entities = system.query.entities\n if(system.sort) {\n copyArray(entities, system.sorted)\n entities = system.sorted\n entities.sort(system.sort)\n }\n system.update(entities, timeDelta, this)\n } else {\n system.update(timeDelta, this)\n }\n }\n\n handleChanges() {\n for(let i = 0; i < this.changed.length; i++) {\n let changed = this.changed[i]\n for(let j = 0; j < this.queries.length; j++) {\n this.queries[j].onChange(changed)\n }\n }\n this.changed.length = 0\n\n for(let i = 0; i < this.removed.length; i++) {\n let removed = this.removed[i]\n for(let j = 0; j < this.queries.length; j++) {\n this.queries[j].onRemove(removed)\n }\n }\n this.changed.length = 0\n }\n}\n\nfunction bothOrNone(a, b) {\n return (a && b) || (!a && !b)\n}\n\nfunction copyArray(from, to) {\n for(let i = 0; i < from.length; ++i) {\n to[i] = from[i]\n }\n to.length = from.length\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/engine.js","import { Key } from './key'\n\nlet index = 0\n\nexport class Entity {\n constructor(componentCount, componentIdMap, onKeyChanged) {\n this.id = index++\n this.components = []\n for(let i = 0; i < componentCount; i++) {\n this.components.push(null)\n }\n this.componentIdMap = componentIdMap\n this.key = new Key(componentCount)\n\n this.prev = null\n this.next = null\n\n this.keyChangeAnnounced = false\n this.onKeyChanged = onKeyChanged\n }\n\n add(component) {\n const index = this.getIndexById(component._id)\n if(this.components[index]) {\n throw new Error('Cannot add another instance of the same component.')\n }\n this.key.setBit(index, true)\n this.components[index] = component\n this.onChange()\n return this\n }\n\n has(Component) {\n const index = this.getIndexById(Component.id)\n return this.components[index] ? true : false\n }\n\n get(Component) {\n const index = this.getIndexById(Component.id)\n const component = this.components[index]\n if(!component) {\n throw new Error('Requested component is not present.')\n }\n return component\n }\n\n remove(Component) {\n const index = this.getIndexById(Component.id)\n const component = this.components[index]\n if(!component) {\n throw new Error('Cannot remove component instance, because it doesn\\'t exist on target entity.')\n }\n Component.destroy(component)\n this.key.setBit(index, false)\n this.components[index] = null\n this.onChange()\n return this\n }\n\n onChange() {\n if(!this.keyChangeAnnounced) {\n this.onKeyChanged(this)\n }\n this.keyChangeAnnounced = true\n }\n\n resetOnKeyChanged() {\n this.keyChangeAnnounced = false\n }\n\n getIndexById(componentId) {\n const index = this.componentIdMap[componentId]\n if(index === undefined) {\n throw new Error('Unknown component passed as argument. Components not included in queries should be manually' +\n ' registered using Engine.registerComponent().')\n }\n return index\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/entity.js","let index = 0\r\n\r\nexport function component(objectConstructor, objectDestructor) {\r\n const id = index++\n objectConstructor.id = id\n objectConstructor.prototype._id = id\n\n objectConstructor.destroy = function(instance) {\n if(objectDestructor) {\n objectDestructor.apply(instance)\n }\n }\n\r\n return objectConstructor\r\n}\r\n\n\n\n// WEBPACK FOOTER //\n// ./src/component.js","import { Key } from './key'\n\nexport const Query = {\n all(...args) {\n return new UnbakedQuery().all(...args)\n }\n}\n\nclass UnbakedQuery {\n constructor() {\n this.components = []\n }\n\n all(...args) {\n if(args.length === 0) {\n throw new Error('The all() method must take at least one argument.')\n }\n this.components.push(...args)\n\n return this\n }\n\n bake(engine) {\n return new BakedQuery(this, engine)\n }\n}\n\nclass BakedQuery {\n constructor({ components }, engine) {\n this.key = new Key(engine.componentsCount + components.length)\n components.forEach(component => {\n engine.registerComponent(component)\n this.key.set(engine.componentIdMap[component.id])\n })\n this.idMap = {}\n this.entities = []\n }\n\n onChange(entity) {\n const index = this.idMap[entity.id]\n const matched = entity.key.matches(this.key)\n if(index === undefined && matched) {\n this.idMap[entity.id] = this.entities.length\n this.entities.push(entity)\n } else if(index !== undefined && !matched) {\n this.remove(entity, index)\n }\n }\n\n onRemove(entity) {\n const index = this.idMap[entity.id]\n if(index !== undefined) {\n this.remove(entity, index)\n }\n }\n\n remove(entity, index) {\n delete this.idMap[entity.id]\n const otherEntity = this.entities.pop()\n if(otherEntity !== entity) {\n this.entities[index] = otherEntity\n this.idMap[otherEntity.id] = index\n }\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/query.js"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack:///webpack/universalModuleDefinition","webpack:///ouyo.js","webpack:///webpack/bootstrap 3d733bed2b58f88d9eca","webpack:///./src/key.js","webpack:///./src/engine.js","webpack:///./src/entity.js","webpack:///./src/component.js","webpack:///./src/query.js"],"names":["root","factory","exports","module","define","amd","this","modules","__webpack_require__","moduleId","installedModules","i","l","call","m","c","d","name","getter","o","Object","defineProperty","configurable","enumerable","get","n","__esModule","object","property","prototype","hasOwnProperty","p","s","__webpack_exports__","_classCallCheck","instance","Constructor","TypeError","Key","_createClass","defineProperties","target","props","length","descriptor","writable","key","protoProps","staticProps","KEY_BITS","POWERS","Math","pow","values","push","value","index","setBit","valueIndex","previousValue","other","currentValue","otherValue","__WEBPACK_IMPORTED_MODULE_0__engine__","__WEBPACK_IMPORTED_MODULE_1__component__","__WEBPACK_IMPORTED_MODULE_2__query__","bothOrNone","a","b","copyArray","from","to","Engine","__WEBPACK_IMPORTED_MODULE_0__entity__","entities","changed","removed","systems","queries","componentIdMap","componentsCount","started","running","onEntityKeyChange","bind","Error","entity","next","prev","_ref","query","sort","update","processEntity","system","bake","sorted","timeDelta","engine","component","id","init","_this","now","lastTime","Date","tick","requestAnimationFrame","runSystem","handleChanges","j","onChange","onRemove","Entity","__WEBPACK_IMPORTED_MODULE_0__key__","componentCount","onKeyChanged","components","keyChangeAnnounced","getIndexById","_id","Component","destroy","componentId","undefined","functionArguments","body","arguments","exec","arg","complexComponent","Function","objectConstructor","objectDestructor","apply","Query","all","UnbakedQuery","_components","BakedQuery","_ref2","forEach","registerComponent","set","idMap","matched","matches","remove","otherEntity","pop"],"mappings":"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","file":"ouyo.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine(\"OUYO\", [], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"OUYO\"] = factory();\n\telse\n\t\troot[\"OUYO\"] = factory();\n})(this, function() {\nreturn \n\n\n// WEBPACK FOOTER //\n// webpack/universalModuleDefinition","(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine(\"OUYO\", [], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"OUYO\"] = factory();\n\telse\n\t\troot[\"OUYO\"] = factory();\n})(this, function() {\nreturn /******/ (function(modules) { // webpackBootstrap\n/******/ \t// The module cache\n/******/ \tvar installedModules = {};\n/******/\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(installedModules[moduleId]) {\n/******/ \t\t\treturn installedModules[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = installedModules[moduleId] = {\n/******/ \t\t\ti: moduleId,\n/******/ \t\t\tl: false,\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/\n/******/ \t\t// Execute the module function\n/******/ \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n/******/\n/******/ \t\t// Flag the module as loaded\n/******/ \t\tmodule.l = true;\n/******/\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/\n/******/\n/******/ \t// expose the modules object (__webpack_modules__)\n/******/ \t__webpack_require__.m = modules;\n/******/\n/******/ \t// expose the module cache\n/******/ \t__webpack_require__.c = installedModules;\n/******/\n/******/ \t// define getter function for harmony exports\n/******/ \t__webpack_require__.d = function(exports, name, getter) {\n/******/ \t\tif(!__webpack_require__.o(exports, name)) {\n/******/ \t\t\tObject.defineProperty(exports, name, {\n/******/ \t\t\t\tconfigurable: false,\n/******/ \t\t\t\tenumerable: true,\n/******/ \t\t\t\tget: getter\n/******/ \t\t\t});\n/******/ \t\t}\n/******/ \t};\n/******/\n/******/ \t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t__webpack_require__.n = function(module) {\n/******/ \t\tvar getter = module && module.__esModule ?\n/******/ \t\t\tfunction getDefault() { return module['default']; } :\n/******/ \t\t\tfunction getModuleExports() { return module; };\n/******/ \t\t__webpack_require__.d(getter, 'a', getter);\n/******/ \t\treturn getter;\n/******/ \t};\n/******/\n/******/ \t// Object.prototype.hasOwnProperty.call\n/******/ \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n/******/\n/******/ \t// __webpack_public_path__\n/******/ \t__webpack_require__.p = \"\";\n/******/\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(__webpack_require__.s = 1);\n/******/ })\n/************************************************************************/\n/******/ ([\n/* 0 */\n/***/ (function(module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"a\", function() { return Key; });\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nvar KEY_BITS = 31;\n\nvar POWERS = [];\nfor (var i = 0; i < KEY_BITS; i++) {\n POWERS[i] = Math.pow(2, i);\n}\n\nvar Key = function () {\n function Key(length) {\n _classCallCheck(this, Key);\n\n this.values = [];\n for (var _i = 0; _i < length / KEY_BITS; _i++) {\n this.values.push(0);\n }\n }\n\n _createClass(Key, [{\n key: \"set\",\n value: function set(index) {\n this.setBit(index, true);\n return this;\n }\n }, {\n key: \"unset\",\n value: function unset(index) {\n this.setBit(index, false);\n return this;\n }\n }, {\n key: \"setBit\",\n value: function setBit(index, value) {\n var valueIndex = 0;\n while (index >= KEY_BITS) {\n valueIndex++;\n index -= KEY_BITS;\n }\n\n var previousValue = (this.values[valueIndex] & POWERS[index]) !== 0;\n if (value && !previousValue) {\n this.values[valueIndex] += POWERS[index];\n } else if (!value && previousValue) {\n this.values[valueIndex] -= POWERS[index];\n }\n }\n }, {\n key: \"matches\",\n value: function matches(other) {\n for (var _i2 = 0; _i2 < other.values.length; _i2++) {\n var currentValue = this.values[_i2] || 0;\n var otherValue = other.values[_i2];\n if ((currentValue & otherValue) !== otherValue) {\n return false;\n }\n }\n return true;\n }\n }]);\n\n return Key;\n}();\n\n/***/ }),\n/* 1 */\n/***/ (function(module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\nObject.defineProperty(__webpack_exports__, \"__esModule\", { value: true });\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__engine__ = __webpack_require__(2);\n/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, \"Engine\", function() { return __WEBPACK_IMPORTED_MODULE_0__engine__[\"a\"]; });\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__component__ = __webpack_require__(4);\n/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, \"component\", function() { return __WEBPACK_IMPORTED_MODULE_1__component__[\"b\"]; });\n/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, \"complexComponent\", function() { return __WEBPACK_IMPORTED_MODULE_1__component__[\"a\"]; });\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__query__ = __webpack_require__(5);\n/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, \"Query\", function() { return __WEBPACK_IMPORTED_MODULE_2__query__[\"a\"]; });\n\n\n\n\n/***/ }),\n/* 2 */\n/***/ (function(module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"a\", function() { return Engine; });\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__entity__ = __webpack_require__(3);\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\n\n\nvar Engine = function () {\n function Engine() {\n _classCallCheck(this, Engine);\n\n this.entities = null;\n this.changed = [];\n this.removed = [];\n\n this.systems = [];\n this.queries = [];\n this.componentIdMap = [];\n this.componentsCount = 0;\n\n this.started = false;\n this.running = false;\n\n this.onEntityKeyChange = this.onEntityKeyChange.bind(this);\n }\n\n _createClass(Engine, [{\n key: 'createEntity',\n value: function createEntity() {\n if (!this.started) {\n throw new Error('Entities cannot be added before the engine is started.');\n }\n\n var entity = new __WEBPACK_IMPORTED_MODULE_0__entity__[\"a\" /* Entity */](this.componentsCount, this.componentIdMap, this.onEntityKeyChange);\n entity.next = this.entities;\n if (this.entities !== null) {\n this.entities.prev = entity;\n }\n this.entities = entity;\n\n return entity;\n }\n }, {\n key: 'removeEntity',\n value: function removeEntity(entity) {\n if (!this.started) {\n throw new Error('Entities cannot be removed before the engine is started.');\n }\n\n var next = entity.next;\n var prev = entity.prev;\n\n if (next !== null) {\n next.prev = entity.prev;\n }\n if (prev !== null) {\n prev.next = entity.next;\n }\n\n entity.next = null;\n entity.prev = null;\n\n if (this.entities = entity) {\n this.entities = next;\n }\n\n this.removed.push(entity);\n }\n }, {\n key: 'onEntityKeyChange',\n value: function onEntityKeyChange(entity) {\n this.changed.push(entity);\n }\n }, {\n key: 'registerSystem',\n value: function registerSystem(_ref) {\n var query = _ref.query,\n sort = _ref.sort,\n update = _ref.update,\n processEntity = _ref.processEntity;\n\n if (this.started) {\n throw new Error('Cannot register system after entities have been added or the engine was started.');\n }\n\n if (bothOrNone(update, processEntity)) {\n throw new Error('Exactly one of \"update\" and \"processEntity\" must be defined on a system.');\n }\n\n if (!query && processEntity) {\n throw new Error('\"processEntity\" can only be used with a query.');\n }\n\n if (!query && sort) {\n throw new Error('\"sort\" can only be used with a query.');\n }\n\n var system = {\n query: query ? query.bake(this) : false,\n sort: sort,\n sorted: [],\n update: update || function (entities, timeDelta, engine) {\n for (var i = 0; i < entities.length; ++i) {\n processEntity(entities[i], timeDelta, engine);\n }\n }\n };\n\n this.systems.push(system);\n\n if (system.query) {\n this.queries.push(system.query);\n }\n }\n }, {\n key: 'registerComponent',\n value: function registerComponent(component) {\n if (this.started) {\n throw new Error('Cannot register component after entities have been added or the engine was started.');\n }\n\n while (component.id >= this.componentIdMap.length) {\n this.componentIdMap.push(null);\n }\n var index = this.componentIdMap[component.id];\n if (index === null) {\n this.componentIdMap[component.id] = this.componentsCount++;\n }\n }\n }, {\n key: 'start',\n value: function start(init) {\n var _this = this;\n\n // TODO: add pause\n this.started = true;\n this.running = true;\n if (init) {\n init(this);\n }\n\n var now = void 0;\n var lastTime = Date.now();\n var tick = function tick() {\n if (_this.running) {\n now = Date.now();\n _this.update((now - lastTime) / 1000);\n lastTime = now;\n }\n requestAnimationFrame(tick);\n };\n requestAnimationFrame(tick);\n }\n }, {\n key: 'update',\n value: function update(timeDelta) {\n for (var i = 0; i < this.systems.length; ++i) {\n this.runSystem(this.systems[i], timeDelta);\n this.handleChanges();\n }\n }\n }, {\n key: 'runSystem',\n value: function runSystem(system, timeDelta) {\n if (system.query) {\n var entities = system.query.entities;\n if (system.sort) {\n copyArray(entities, system.sorted);\n entities = system.sorted;\n entities.sort(system.sort);\n }\n system.update(entities, timeDelta, this);\n } else {\n system.update(timeDelta, this);\n }\n }\n }, {\n key: 'handleChanges',\n value: function handleChanges() {\n for (var i = 0; i < this.changed.length; i++) {\n var changed = this.changed[i];\n for (var j = 0; j < this.queries.length; j++) {\n this.queries[j].onChange(changed);\n }\n }\n this.changed.length = 0;\n\n for (var _i = 0; _i < this.removed.length; _i++) {\n var removed = this.removed[_i];\n for (var _j = 0; _j < this.queries.length; _j++) {\n this.queries[_j].onRemove(removed);\n }\n }\n this.changed.length = 0;\n }\n }]);\n\n return Engine;\n}();\n\nfunction bothOrNone(a, b) {\n return a && b || !a && !b;\n}\n\nfunction copyArray(from, to) {\n for (var i = 0; i < from.length; ++i) {\n to[i] = from[i];\n }\n to.length = from.length;\n}\n\n/***/ }),\n/* 3 */\n/***/ (function(module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"a\", function() { return Entity; });\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__key__ = __webpack_require__(0);\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\n\n\nvar index = 0;\n\nvar Entity = function () {\n function Entity(componentCount, componentIdMap, onKeyChanged) {\n _classCallCheck(this, Entity);\n\n this.id = index++;\n this.components = [];\n for (var i = 0; i < componentCount; i++) {\n this.components.push(null);\n }\n this.componentIdMap = componentIdMap;\n this.key = new __WEBPACK_IMPORTED_MODULE_0__key__[\"a\" /* Key */](componentCount);\n\n this.prev = null;\n this.next = null;\n\n this.keyChangeAnnounced = false;\n this.onKeyChanged = onKeyChanged;\n }\n\n _createClass(Entity, [{\n key: 'add',\n value: function add(component) {\n var index = this.getIndexById(component._id);\n if (this.components[index]) {\n throw new Error('Cannot add another instance of the same component.');\n }\n this.key.setBit(index, true);\n this.components[index] = component;\n this.onChange();\n return this;\n }\n }, {\n key: 'has',\n value: function has(Component) {\n var index = this.getIndexById(Component.id);\n return this.components[index] ? true : false;\n }\n }, {\n key: 'get',\n value: function get(Component) {\n var index = this.getIndexById(Component.id);\n var component = this.components[index];\n if (!component) {\n throw new Error('Requested component is not present.');\n }\n return component;\n }\n }, {\n key: 'remove',\n value: function remove(Component) {\n var index = this.getIndexById(Component.id);\n var component = this.components[index];\n if (!component) {\n throw new Error('Cannot remove component instance, because it doesn\\'t exist on target entity.');\n }\n Component.destroy(component);\n this.key.setBit(index, false);\n this.components[index] = null;\n this.onChange();\n return this;\n }\n }, {\n key: 'onChange',\n value: function onChange() {\n if (!this.keyChangeAnnounced) {\n this.onKeyChanged(this);\n }\n this.keyChangeAnnounced = true;\n }\n }, {\n key: 'resetOnKeyChanged',\n value: function resetOnKeyChanged() {\n this.keyChangeAnnounced = false;\n }\n }, {\n key: 'getIndexById',\n value: function getIndexById(componentId) {\n var index = this.componentIdMap[componentId];\n if (index === undefined) {\n throw new Error('Unknown component passed as argument. Components not included in queries should be manually' + ' registered using Engine.registerComponent().');\n }\n return index;\n }\n }]);\n\n return Entity;\n}();\n\n/***/ }),\n/* 4 */\n/***/ (function(module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n/* harmony export (immutable) */ __webpack_exports__[\"b\"] = component;\n/* harmony export (immutable) */ __webpack_exports__[\"a\"] = complexComponent;\nvar index = 0;\n\nfunction component() {\n var functionArguments = [];\n var body = '';\n for (var i = 0; i < arguments.length; i++) {\n if (/^[_a-zA-Z]\\w*$/.exec(arguments[i])) {\n var arg = arguments[i];\n functionArguments[i] = arg;\n body += 'this.' + arg + '=' + arg + ';';\n } else {\n throw new Error('Invalid identifier: ' + arguments[i]);\n }\n }\n return complexComponent(new Function(functionArguments, body));\n}\n\nfunction complexComponent(objectConstructor, objectDestructor) {\n var id = index++;\n objectConstructor.id = id;\n objectConstructor.prototype._id = id;\n\n objectConstructor.destroy = function (instance) {\n if (objectDestructor) {\n objectDestructor.apply(instance);\n }\n };\n\n return objectConstructor;\n}\n\n/***/ }),\n/* 5 */\n/***/ (function(module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"a\", function() { return Query; });\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__key__ = __webpack_require__(0);\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\n\n\nvar Query = {\n all: function all() {\n var _ref;\n\n return (_ref = new UnbakedQuery()).all.apply(_ref, arguments);\n }\n};\n\nvar UnbakedQuery = function () {\n function UnbakedQuery() {\n _classCallCheck(this, UnbakedQuery);\n\n this.components = [];\n }\n\n _createClass(UnbakedQuery, [{\n key: 'all',\n value: function all() {\n var _components;\n\n if (arguments.length === 0) {\n throw new Error('The all() method must take at least one argument.');\n }\n (_components = this.components).push.apply(_components, arguments);\n\n return this;\n }\n }, {\n key: 'bake',\n value: function bake(engine) {\n return new BakedQuery(this, engine);\n }\n }]);\n\n return UnbakedQuery;\n}();\n\nvar BakedQuery = function () {\n function BakedQuery(_ref2, engine) {\n var _this = this;\n\n var components = _ref2.components;\n\n _classCallCheck(this, BakedQuery);\n\n this.key = new __WEBPACK_IMPORTED_MODULE_0__key__[\"a\" /* Key */](engine.componentsCount + components.length);\n components.forEach(function (component) {\n engine.registerComponent(component);\n _this.key.set(engine.componentIdMap[component.id]);\n });\n this.idMap = {};\n this.entities = [];\n }\n\n _createClass(BakedQuery, [{\n key: 'onChange',\n value: function onChange(entity) {\n var index = this.idMap[entity.id];\n var matched = entity.key.matches(this.key);\n if (index === undefined && matched) {\n this.idMap[entity.id] = this.entities.length;\n this.entities.push(entity);\n } else if (index !== undefined && !matched) {\n this.remove(entity, index);\n }\n }\n }, {\n key: 'onRemove',\n value: function onRemove(entity) {\n var index = this.idMap[entity.id];\n if (index !== undefined) {\n this.remove(entity, index);\n }\n }\n }, {\n key: 'remove',\n value: function remove(entity, index) {\n delete this.idMap[entity.id];\n var otherEntity = this.entities.pop();\n if (otherEntity !== entity) {\n this.entities[index] = otherEntity;\n this.idMap[otherEntity.id] = index;\n }\n }\n }]);\n\n return BakedQuery;\n}();\n\n/***/ })\n/******/ ]);\n});\n\n\n// WEBPACK FOOTER //\n// ouyo.js"," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 1);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 3d733bed2b58f88d9eca","const KEY_BITS = 31\r\n\r\nconst POWERS = []\r\nfor(let i = 0; i < KEY_BITS; i++) {\r\n POWERS[i] = 2 ** i\r\n}\r\n\r\nexport class Key {\r\n constructor(length) {\r\n this.values = []\r\n for(let i = 0; i < length / KEY_BITS; i++) {\r\n this.values.push(0)\r\n }\r\n }\r\n\r\n set(index) {\r\n this.setBit(index, true)\r\n return this\r\n }\r\n\r\n unset(index) {\r\n this.setBit(index, false)\r\n return this\r\n }\r\n\r\n setBit(index, value) {\r\n let valueIndex = 0\r\n while(index >= KEY_BITS) {\r\n valueIndex++\r\n index -= KEY_BITS\r\n }\r\n\r\n const previousValue = (this.values[valueIndex] & POWERS[index]) !== 0\r\n if(value && !previousValue) {\r\n this.values[valueIndex] += POWERS[index]\r\n } else if(!value && previousValue) {\r\n this.values[valueIndex] -= POWERS[index]\r\n }\r\n }\r\n\r\n matches(other) {\r\n for(let i = 0; i < other.values.length; i++) {\r\n const currentValue = this.values[i] || 0\r\n const otherValue = other.values[i]\r\n if((currentValue & otherValue) !== otherValue) {\r\n return false\r\n }\r\n }\r\n return true\r\n }\r\n}\r\n\n\n\n// WEBPACK FOOTER //\n// ./src/key.js","import { Entity } from './entity'\n\nexport class Engine {\n constructor() {\n this.entities = null\n this.changed = []\n this.removed = []\n\n this.systems = []\n this.queries = []\n this.componentIdMap = []\n this.componentsCount = 0\n\n this.started = false\n this.running = false\n\n this.onEntityKeyChange = this.onEntityKeyChange.bind(this)\n }\n\n createEntity() {\n if(!this.started) {\n throw new Error('Entities cannot be added before the engine is started.')\n }\n\n const entity = new Entity(\n this.componentsCount,\n this.componentIdMap,\n this.onEntityKeyChange\n )\n entity.next = this.entities\n if(this.entities !== null) {\n this.entities.prev = entity\n }\n this.entities = entity\n\n return entity\n }\n\n removeEntity(entity) {\n if(!this.started) {\n throw new Error('Entities cannot be removed before the engine is started.')\n }\n\n const next = entity.next\n const prev = entity.prev\n\n if(next !== null) {\n next.prev = entity.prev\n }\n if(prev !== null) {\n prev.next = entity.next\n }\n\n entity.next = null\n entity.prev = null\n\n if(this.entities = entity) {\n this.entities = next\n }\n\n this.removed.push(entity)\n }\n\n onEntityKeyChange(entity) {\n this.changed.push(entity)\n }\n\n registerSystem({ query, sort, update, processEntity }) {\n if(this.started) {\n throw new Error('Cannot register system after entities have been added or the engine was started.')\n }\n\n if(bothOrNone(update, processEntity)) {\n throw new Error('Exactly one of \"update\" and \"processEntity\" must be defined on a system.')\n }\n\n if(!query && processEntity) {\n throw new Error('\"processEntity\" can only be used with a query.')\n }\n\n if(!query && sort) {\n throw new Error('\"sort\" can only be used with a query.')\n }\n\n const system = {\n query: query ? query.bake(this) : false,\n sort,\n sorted: [],\n update: update || function(entities, timeDelta, engine) {\n for(let i = 0; i < entities.length; ++i) {\n processEntity(entities[i], timeDelta, engine)\n }\n }\n }\n\n this.systems.push(system)\n\n if(system.query) {\n this.queries.push(system.query)\n }\n }\n\n registerComponent(component) {\n if(this.started) {\n throw new Error('Cannot register component after entities have been added or the engine was started.')\n }\n\n while(component.id >= this.componentIdMap.length) {\n this.componentIdMap.push(null)\n }\n const index = this.componentIdMap[component.id]\n if(index === null) {\n this.componentIdMap[component.id] = this.componentsCount++\n }\n }\n\n start(init) {\n // TODO: add pause\n this.started = true\n this.running = true\n if(init) {\n init(this)\n }\n\n let now\n let lastTime = Date.now()\n const tick = () => {\n if(this.running) {\n now = Date.now()\n this.update((now - lastTime) / 1000)\n lastTime = now\n }\n requestAnimationFrame(tick)\n }\n requestAnimationFrame(tick)\n }\n\n update(timeDelta) {\n for(let i = 0; i < this.systems.length; ++i) {\n this.runSystem(this.systems[i], timeDelta)\n this.handleChanges()\n }\n }\n\n runSystem(system, timeDelta) {\n if(system.query) {\n let entities = system.query.entities\n if(system.sort) {\n copyArray(entities, system.sorted)\n entities = system.sorted\n entities.sort(system.sort)\n }\n system.update(entities, timeDelta, this)\n } else {\n system.update(timeDelta, this)\n }\n }\n\n handleChanges() {\n for(let i = 0; i < this.changed.length; i++) {\n let changed = this.changed[i]\n for(let j = 0; j < this.queries.length; j++) {\n this.queries[j].onChange(changed)\n }\n }\n this.changed.length = 0\n\n for(let i = 0; i < this.removed.length; i++) {\n let removed = this.removed[i]\n for(let j = 0; j < this.queries.length; j++) {\n this.queries[j].onRemove(removed)\n }\n }\n this.changed.length = 0\n }\n}\n\nfunction bothOrNone(a, b) {\n return (a && b) || (!a && !b)\n}\n\nfunction copyArray(from, to) {\n for(let i = 0; i < from.length; ++i) {\n to[i] = from[i]\n }\n to.length = from.length\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/engine.js","import { Key } from './key'\n\nlet index = 0\n\nexport class Entity {\n constructor(componentCount, componentIdMap, onKeyChanged) {\n this.id = index++\n this.components = []\n for(let i = 0; i < componentCount; i++) {\n this.components.push(null)\n }\n this.componentIdMap = componentIdMap\n this.key = new Key(componentCount)\n\n this.prev = null\n this.next = null\n\n this.keyChangeAnnounced = false\n this.onKeyChanged = onKeyChanged\n }\n\n add(component) {\n const index = this.getIndexById(component._id)\n if(this.components[index]) {\n throw new Error('Cannot add another instance of the same component.')\n }\n this.key.setBit(index, true)\n this.components[index] = component\n this.onChange()\n return this\n }\n\n has(Component) {\n const index = this.getIndexById(Component.id)\n return this.components[index] ? true : false\n }\n\n get(Component) {\n const index = this.getIndexById(Component.id)\n const component = this.components[index]\n if(!component) {\n throw new Error('Requested component is not present.')\n }\n return component\n }\n\n remove(Component) {\n const index = this.getIndexById(Component.id)\n const component = this.components[index]\n if(!component) {\n throw new Error('Cannot remove component instance, because it doesn\\'t exist on target entity.')\n }\n Component.destroy(component)\n this.key.setBit(index, false)\n this.components[index] = null\n this.onChange()\n return this\n }\n\n onChange() {\n if(!this.keyChangeAnnounced) {\n this.onKeyChanged(this)\n }\n this.keyChangeAnnounced = true\n }\n\n resetOnKeyChanged() {\n this.keyChangeAnnounced = false\n }\n\n getIndexById(componentId) {\n const index = this.componentIdMap[componentId]\n if(index === undefined) {\n throw new Error('Unknown component passed as argument. Components not included in queries should be manually' +\n ' registered using Engine.registerComponent().')\n }\n return index\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/entity.js","let index = 0\r\n\nexport function component() {\n let functionArguments = []\n let body = ''\n for(let i = 0; i < arguments.length; i++) {\n if(/^[_a-zA-Z]\\w*$/.exec(arguments[i])) {\n let arg = arguments[i]\n functionArguments[i] = arg\n body += `this.${arg}=${arg};`\n } else {\n throw new Error('Invalid identifier: ' + arguments[i])\n }\n }\n return complexComponent(new Function(functionArguments, body))\n}\n\nexport function complexComponent(objectConstructor, objectDestructor) {\n const id = index++\n objectConstructor.id = id\n objectConstructor.prototype._id = id\n\n objectConstructor.destroy = function(instance) {\n if(objectDestructor) {\n objectDestructor.apply(instance)\n }\n }\n\n return objectConstructor\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/component.js","import { Key } from './key'\n\nexport const Query = {\n all(...args) {\n return new UnbakedQuery().all(...args)\n }\n}\n\nclass UnbakedQuery {\n constructor() {\n this.components = []\n }\n\n all(...args) {\n if(args.length === 0) {\n throw new Error('The all() method must take at least one argument.')\n }\n this.components.push(...args)\n\n return this\n }\n\n bake(engine) {\n return new BakedQuery(this, engine)\n }\n}\n\nclass BakedQuery {\n constructor({ components }, engine) {\n this.key = new Key(engine.componentsCount + components.length)\n components.forEach(component => {\n engine.registerComponent(component)\n this.key.set(engine.componentIdMap[component.id])\n })\n this.idMap = {}\n this.entities = []\n }\n\n onChange(entity) {\n const index = this.idMap[entity.id]\n const matched = entity.key.matches(this.key)\n if(index === undefined && matched) {\n this.idMap[entity.id] = this.entities.length\n this.entities.push(entity)\n } else if(index !== undefined && !matched) {\n this.remove(entity, index)\n }\n }\n\n onRemove(entity) {\n const index = this.idMap[entity.id]\n if(index !== undefined) {\n this.remove(entity, index)\n }\n }\n\n remove(entity, index) {\n delete this.idMap[entity.id]\n const otherEntity = this.entities.pop()\n if(otherEntity !== entity) {\n this.entities[index] = otherEntity\n this.idMap[otherEntity.id] = index\n }\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/query.js"],"sourceRoot":""} \ No newline at end of file diff --git a/package.json b/package.json index da5174d..232bf44 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "system" ], "license": "MIT", - "version": "0.3.1", + "version": "0.4.0", "main": "lib/ouyo.js", "repository": { "type": "git", diff --git a/src/component.js b/src/component.js index b1678b8..1dd533d 100644 --- a/src/component.js +++ b/src/component.js @@ -1,6 +1,21 @@ let index = 0 -export function component(objectConstructor, objectDestructor) { +export function component() { + let functionArguments = [] + let body = '' + for(let i = 0; i < arguments.length; i++) { + if(/^[_a-zA-Z]\w*$/.exec(arguments[i])) { + let arg = arguments[i] + functionArguments[i] = arg + body += `this.${arg}=${arg};` + } else { + throw new Error('Invalid identifier: ' + arguments[i]) + } + } + return complexComponent(new Function(functionArguments, body)) +} + +export function complexComponent(objectConstructor, objectDestructor) { const id = index++ objectConstructor.id = id objectConstructor.prototype._id = id diff --git a/src/component.test.js b/src/component.test.js index 3c2f22b..48f650b 100644 --- a/src/component.test.js +++ b/src/component.test.js @@ -1,12 +1,39 @@ -import { component } from './component' +import { component, complexComponent } from './component' describe('component', () => { + test('the result should be a constructor', () => { + const TestComponent = component('a', 'b') + expect(new TestComponent(1, 2)).toEqual({ a: 1, b: 2 }) + }) + + test('the result have and id property', () => { + const TestComponent = component('a', 'b') + const instance = new TestComponent(1, 2) + + expect(TestComponent.id).not.toBe(undefined) + expect(TestComponent.id).toEqual(instance._id) + }) + + test('must work along complexComponent', () => { + const SimpleComponent = component('a', 'b') + const ComplexComponent = complexComponent( + function(a, b) { + this.a = a + this.b = b + } + ) + expect(ComplexComponent.id).toEqual(SimpleComponent.id + 1) + expect(SimpleComponent.destroy).toBeInstanceOf(Function) + }) +}) + +describe('complexComponent', () => { test('should modify the prototype of the argument', () => { function Undecorated(a, b) { this.a = a this.b = b } - const Decorated = component(Undecorated) + const Decorated = complexComponent(Undecorated) expect(new Decorated(1, 2)._id).toBe(Decorated.id) }) @@ -16,7 +43,7 @@ describe('component', () => { this.a = a this.b = b } - const Decorated = component(Undecorated) + const Decorated = complexComponent(Undecorated) expect(new Decorated(1, 2)).toEqual({ a: 1, b: 2 }) }) @@ -26,23 +53,23 @@ describe('component', () => { this.a = a this.b = b } - const DecoratedA = component(Undecorated) - const DecoratedB = component(Undecorated) + const DecoratedA = complexComponent(Undecorated) + const DecoratedB = complexComponent(Undecorated) expect(DecoratedA).toBe(DecoratedB) }) test('consecutive calls increment the id', () => { - const DecoratedA = component(function() {}) - const DecoratedB = component(function() {}) - const DecoratedC = component(function() {}) + const DecoratedA = complexComponent(function() {}) + const DecoratedB = complexComponent(function() {}) + const DecoratedC = complexComponent(function() {}) expect(DecoratedB.id).toEqual(DecoratedA.id + 1) expect(DecoratedC.id).toBe(DecoratedB.id + 1) }) test('should allow object destruction', () => { - const Component = component(function (a, b) { + const Component = complexComponent(function (a, b) { this.a = a this.b = b }) @@ -52,7 +79,7 @@ describe('component', () => { test('upon destruction calls the second parameter', () => { let innerInstance - const Component = component(function (a, b) { + const Component = complexComponent(function (a, b) { this.a = a this.b = b }, function() { diff --git a/src/entity.test.js b/src/entity.test.js index cab68b9..d43a9f9 100644 --- a/src/entity.test.js +++ b/src/entity.test.js @@ -3,8 +3,8 @@ import { Key } from './key' import { component } from './component' const noop = () => {} -const ComponentA = component(function() {}) -const ComponentB = component(function() {}) +const ComponentA = component() +const ComponentB = component() const idMap = {} idMap[ComponentA.id] = 0 idMap[ComponentB.id] = 1 @@ -104,7 +104,7 @@ describe('Entity', () => { }) test('using unknown component results in exception', () => { - const ComponentC = component(function() {}) + const ComponentC = component() const entity = new Entity(count, idMap, noop) expect(() => entity.add(new ComponentC())).toThrow() diff --git a/src/index.js b/src/index.js index cfdcc22..f445988 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,3 @@ export { Engine } from './engine' -export { component } from './component' +export { component, complexComponent } from './component' export { Query } from './query' diff --git a/src/query.test.js b/src/query.test.js index 32ce71d..ce5ab7e 100644 --- a/src/query.test.js +++ b/src/query.test.js @@ -3,9 +3,9 @@ import { component } from './component' import { Engine } from './engine' import { Key } from './key' -const ComponentA = component(function() {}) -const ComponentB = component(function() {}) -const ComponentC = component(function() {}) +const ComponentA = component() +const ComponentB = component() +const ComponentC = component() describe('Query', () => { test('all should create an object with bake method', () => {