Source: packages/ecs-js/component.js

  1. const ComponentRefs = require('./componentrefs');
  2. const UUID = require('uuid/v1');
  3. const CoreProperties = new Set([
  4. 'ecs', 'entity', 'type', '_values', '_ready', 'id',
  5. 'updated', 'constructor', 'stringify', 'clone', 'getObject'
  6. ]);
  7. /**ECS base class of component
  8. * @class BaseComponent
  9. */
  10. class BaseComponent {
  11. constructor(ecs, entity, initialValues) {
  12. Object.defineProperty(this, 'ecs', { enumerable: false, value: ecs });
  13. Object.defineProperty(this, 'entity', { enumerable: true, value: entity });
  14. Object.defineProperty(this, 'type', { enumerable: false, value: this.constructor.name });
  15. Object.defineProperty(this, '_values', { enumerable: false, value: {} });
  16. Object.defineProperty(this, '_refs', { enumerable: false, value: {} });
  17. Object.defineProperty(this, '_ready', { writable: true, enumerable: false, value: false });
  18. Object.defineProperty(this, 'id', { enumerable: true, value: initialValues.id || UUID() });
  19. Object.defineProperty(this, 'updated', { enumerable: false, writable: true, value: this.ecs.ticks });
  20. //loop through inheritance by way of prototypes
  21. //avoiding constructor->super() boilerplate for every component
  22. //also avoiding proxies just for a simple setter on properties
  23. const definitions = [];
  24. for (var c = this.constructor; c !== null; c = Object.getPrototypeOf(c)) {
  25. if (!c.definition) continue;
  26. definitions.push(c.definition);
  27. }
  28. //we want to inherit deep prototype defintions first
  29. definitions.reverse();
  30. for (let idx = 0, l = definitions.length; idx < l; idx++) {
  31. const definition = definitions[idx];
  32. // set component properties from Component.properties
  33. if (!definition.properties) {
  34. continue;
  35. }
  36. const properties = definition.properties;
  37. const keys = Object.keys(properties);
  38. for (let idx = 0, l = keys.length; idx < l; idx++) {
  39. const property = keys[idx];
  40. if (CoreProperties.has(property)) {
  41. throw new Error(`Cannot override property in Component definition: ${property}`);
  42. }
  43. const value = properties[property];
  44. if (this._values.hasOwnProperty(property)) {
  45. this[property] = value;
  46. continue;
  47. }
  48. switch (value) {
  49. case '<EntitySet>':
  50. Object.defineProperty(this, property, {
  51. //writable: true,
  52. enumerable: true,
  53. set: (value) => {
  54. Reflect.set(this._values, property, ComponentRefs.EntitySet(value, this, property));
  55. },
  56. get: () => {
  57. return Reflect.get(this._values, property);
  58. }
  59. });
  60. //this._refs[property] = this[property];
  61. this[property] = [];
  62. break;
  63. case '<EntityObject>':
  64. Object.defineProperty(this, property, {
  65. writable: false,
  66. enumerable: true,
  67. value: ComponentRefs.EntityObject({}, this, property)
  68. });
  69. this._refs[property] = this[property];
  70. break;
  71. case '<Entity>':
  72. Object.defineProperty(this, property, {
  73. enumerable: true,
  74. writeable: true,
  75. set: (value) => {
  76. if (value && value.id) {
  77. value = value.id;
  78. }
  79. const old = Reflect.get(this._values, property);
  80. if (old && old !== value) {
  81. this.ecs.deleteRef(old, this.entity.id, this.id, property);
  82. }
  83. if (value && value !== old) {
  84. this.ecs.addRef(value, this.entity.id, this.id, property);
  85. }
  86. const result = Reflect.set(this._values, property, value);
  87. this.ecs._sendChange(this, 'setEntity', property, old, value);
  88. return result;
  89. },
  90. get: () => {
  91. return this.ecs.getEntity(this._values[property]);
  92. }
  93. });
  94. this._values[property] = null;
  95. break;
  96. case '<ComponentObject>':
  97. Object.defineProperty(this, property, {
  98. writable: false,
  99. enumerable: true,
  100. value: ComponentRefs.ComponentObject({}, this)
  101. });
  102. this._refs[property] = this[property];
  103. break;
  104. case '<ComponentSet>':
  105. Object.defineProperty(this, property, {
  106. //writable: true,
  107. enumerable: true,
  108. set: (value) => {
  109. Reflect.set(this._values, property, ComponentRefs.ComponentSet(value, this, property));
  110. },
  111. get: () => {
  112. return Reflect.get(this._values, property);
  113. }
  114. });
  115. //this._refs[property] = this[property];
  116. this[property] = [];
  117. break;
  118. case '<Component>':
  119. Object.defineProperty(this, property, {
  120. enumerable: true,
  121. writeable: true,
  122. set: (value) => {
  123. if (typeof value === 'object') {
  124. value = value.id;
  125. }
  126. const old = Reflect.get(this._values, property);
  127. const result = Reflect.set(this._values, property, value);
  128. this.ecs._sendChange(this, 'setComponent', property, old, value);
  129. return result;
  130. },
  131. get: () => {
  132. return this.entity.componentMap[this._values[property]];
  133. }
  134. });
  135. this._values[property] = null;
  136. break;
  137. default:
  138. let reflect = null;
  139. if (typeof value === 'string' && value.startsWith('<Pointer ')) {
  140. reflect = value.substring(9, value.length - 1).trim().split('.')
  141. }
  142. Object.defineProperty(this, property, {
  143. enumerable: true,
  144. writeable: true,
  145. set: (value) => {
  146. const old = Reflect.get(this._values, property, value);
  147. const result = Reflect.set(this._values, property, value);
  148. if (reflect) {
  149. let node = this;
  150. let fail = false;
  151. for (let i = 0; i < reflect.length - 1; i++) {
  152. const subprop = reflect[i];
  153. /* $lab:coverage:off$ */
  154. if (typeof node === 'object' && node !== null && node.hasOwnProperty(subprop)) {
  155. /* $lab:coverage:on */
  156. node = node[subprop];
  157. } else {
  158. fail = true;
  159. }
  160. }
  161. if (!fail) {
  162. Reflect.set(node, reflect[reflect.length - 1], value);
  163. node = value;
  164. }
  165. }
  166. this.ecs._sendChange(this, 'set', property, old, value);
  167. return result;
  168. },
  169. get: () => {
  170. if (!reflect) {
  171. return Reflect.get(this._values, property);
  172. }
  173. let node = this;
  174. let fail = false;
  175. for (let i = 0; i < reflect.length - 1; i++) {
  176. const subprop = reflect[i];
  177. /* $lab:coverage:off$ */
  178. if (typeof node === 'object' && node !== null && node.hasOwnProperty(subprop)) {
  179. /* $lab:coverage:on */
  180. node = node[subprop];
  181. } else {
  182. fail = true;
  183. }
  184. }
  185. if (!fail) {
  186. return Reflect.get(node, reflect[reflect.length - 1]);
  187. } else {
  188. return Reflect.get(this._values, property);
  189. }
  190. }
  191. });
  192. this._values[property] = value;
  193. break;
  194. }
  195. }
  196. }
  197. // don't allow new properties
  198. Object.seal(this);
  199. Object.seal(this._values);
  200. const values = { ...initialValues };
  201. delete values.type;
  202. delete values.entity;
  203. delete values.id;
  204. Object.assign(this, values);
  205. this.ecs._sendChange(this, 'addComponent');
  206. this._ready = true;
  207. }
  208. stringify() {
  209. return JSON.stringify(this.getObject());
  210. }
  211. getObject() {
  212. const serialize = this.constructor.definition.serialize;
  213. let values = this._values;
  214. if (serialize) {
  215. /* $lab:coverage:off$ */
  216. if (serialize.skip) return undefined;
  217. /* $lab:coverage:on$ */
  218. if (serialize.ignore.length > 0) {
  219. values = {}
  220. const props = new Set([...serialize.ignore]);
  221. for (const prop of Object.keys(this._values).filter(prop => !props.has(prop))) {
  222. values[prop] = this._values[prop];
  223. }
  224. }
  225. }
  226. return Object.assign({ id: this.id, type: this.type }, values, this._refs);
  227. }
  228. }
  229. BaseComponent.definition = {
  230. properties: {
  231. },
  232. multiset: false,
  233. serialize: {
  234. skip: false,
  235. ignore: [],
  236. }
  237. };
  238. module.exports = BaseComponent;