@perplexdotgg/mecs 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,8 @@
1
+ [![npm version](https://img.shields.io/npm/v/@perplexdotgg/mecs.svg)](https://www.npmjs.com/package/@perplexdotgg/mecs)
2
+ [![repo](https://img.shields.io/badge/codeberg-repo-7d48ff?logo=codeberg&logoColor=ff7d48)](https://codeberg.org/perplexdotgg/mecs)
3
+ [![license](https://img.shields.io/npm/l/@perplexdotgg/mecs.svg?color=7d48ff)](http://codeberg.org/perplexdotgg/mecs/src/branch/main/LICENSE)
4
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-7d48ff.svg)](https://codeberg.org/perplexdotgg/mecs/pulls)
5
+
1
6
  <div style="display: flex;">
2
7
  <img src="./mecs.svg" width="152" height="64" style="margin-right: 16px;" />
3
8
  <h1>MECS - Monomorph Entity Component System</h1>
@@ -187,9 +192,9 @@ const entity1 = Entity.create({
187
192
  },
188
193
  });
189
194
 
190
- // now entity1 is of the Entity class, and entity1.localTransform is of the Transform class
195
+ // now entity1 is of the Entity class, and entity1.globalTransform is of the Transform class
191
196
  // so you can call methods on it like so:
192
- entity1.localTransform.composeMatrix();
197
+ entity1.globalTransform.composeMatrix();
193
198
 
194
199
  const entity2 = Entity.create({
195
200
  projectile: {
@@ -198,7 +203,7 @@ const entity2 = Entity.create({
198
203
  },
199
204
  });
200
205
 
201
- // destroy() to return the entity to the pool, it will automatically be re-used later
206
+ // you can use destroy() to return an entity to the pool, it will automatically be re-used later
202
207
  // this iterates on all components to remove them first, and triggers queries just
203
208
  // before the component(s) needed for the query are deleted
204
209
  // each destroyed component also gets recycled in its own component pool
@@ -243,7 +248,7 @@ const components = {
243
248
  // component config options here, see "Customizing Components" below
244
249
  },
245
250
 
246
- } as const satisfies ComponentMap;
251
+ };
247
252
 
248
253
  const queries = {};
249
254
 
@@ -375,6 +380,41 @@ Entity.queries.projectiles.beforeEntityRemoved.removeListener(someListener);
375
380
 
376
381
  ```
377
382
 
383
+ ### Components that reference Entities
384
+ You can have components that reference other entities, by using Monomorph's `LazyReferenceType` and `LazyReferenceListType`. This will usually 'just work', but in some situations you may also need to use MECS's `LazyComponent`. See the Typescript tests in [`tests/componentsWithEntityReferences.test.ts`](https://codeberg.org/perplexdotgg/mecs/src/branch/main/tests/componentsWithEntityReferences.test.ts) and [`tests/lazyComponentsWithEntityReferences.test.ts`](https://codeberg.org/perplexdotgg/mecs/src/branch/main/tests/lazyComponentsWithEntityReferences.test.ts) for examples. Below is a simple Javascript example:
385
+
386
+ ```js
387
+ const turretProps = {
388
+ targetEntity: LazyReferenceType(() => Entity),
389
+
390
+ // or for multiple referenced entities:
391
+ targetEntities: LazyReferenceListType(() => Entity),
392
+ };
393
+ class Turret extends createClass(turretProps) {}
394
+
395
+ const components = {
396
+ turret: Turret,
397
+ // or the lazy loaded version:
398
+ // turret: LazyComponent(() => Turret),
399
+
400
+ // alternatively, you can use a component config object for further customization:
401
+ turret: {
402
+ monomorphClass: Turret,
403
+ // or the lazy loaded version:
404
+ // monomorphClass: LazyComponent(() => Turret),
405
+
406
+ // other config options here, for example:
407
+ afterComponentAdded: (turretInstance, entity, componentKey) => {
408
+ if (turretInstance.targetEntity === null) {
409
+ // example maybe automatically select a target in this situation
410
+ }
411
+ },
412
+ },
413
+ };
414
+ class Entity extends createEntityClass()(components, queries) {}
415
+ ```
416
+
417
+
378
418
  ### Customizing Components
379
419
  For both monomorph and non-monomorph types, you can pass a component config object, allowing you to customize the component's lifecycle. A brief example is below with a few of the options, for many more option examples, see the tests in
380
420
  [`tests/customComponents.test.ts`](https://codeberg.org/perplexdotgg/mecs/src/branch/main/tests/customComponents.test.ts).
@@ -486,6 +526,10 @@ const entity2 = Entity.create({
486
526
 
487
527
  </details>
488
528
 
529
+ ---
530
+
531
+ Here are all the available component config options:
532
+
489
533
  | Option | Type | Description |
490
534
  | --- | --- | --- |
491
535
  | `monomorphClass` | A [Monomorph](http://codeberg.org/perplexdotgg/monomorph) class | If you want to customize the component lifecycle for a monomorph class, set this to the monomorphClass. Otherwise do not set this field. |
@@ -508,4 +552,4 @@ const entity2 = Entity.create({
508
552
 
509
553
  ## How to contribute
510
554
 
511
- If you like this project and would like to support our work, please consider contributing code via pull requests, or donating via [open collective](https://opencollective.com/perplexgg). Contributions are greatly appreciated!
555
+ If you like this project and would like to support our work, please consider contributing code via [pull requests](https://codeberg.org/perplexdotgg/mecs/pulls), or donating via [open collective](https://opencollective.com/perplexgg). Contributions are greatly appreciated!
package/build/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { InputType } from 'monomorph';
2
2
  import { MonomorphClass } from 'monomorph';
3
3
  import { MonomorphInstance } from 'monomorph';
4
+ import { NumberArray } from 'monomorph';
4
5
  import { WithPool } from 'monomorph';
5
6
 
6
7
  export declare type ComponentConfig<ComponentType, ComponentInputType extends any = InputType<ComponentType> extends never ? ComponentType : InputType<ComponentType>> = {
@@ -36,23 +37,26 @@ export declare type ComponentConfigFunction<ComponentType, ComponentInputType> =
36
37
 
37
38
  export declare type ComponentInput<X> = X extends MonomorphClass<infer P, infer I, infer M> ? M extends MonomorphInstance<infer iP, infer iI> ? InputType<M> : never : X extends ComponentConfig<infer CT, infer I> ? I : X;
38
39
 
39
- export declare type ComponentMap = Record<string, ComponentConfig<any, any> | MonomorphClass<any, any, any> | MonomorphClass<any, any, any>>;
40
+ export declare type ComponentMap = Record<string, ComponentConfig<any, any> | MonomorphClass<any, any, any> | LazyWrapper<MonomorphClass<any, any, any>> | ComponentConfig<MonomorphClass<any, any, any>, any> | ComponentConfig<LazyWrapper<MonomorphClass<any, any, any>>, any>>;
40
41
 
41
42
  declare type ComponentMapClassProperties<CM> = {
42
- [K in keyof CM as LowercaseFirstLetter<K>]?: CM[K] extends MonomorphClass<infer P, infer I, infer M> ? M : CM[K] extends ComponentConfig<infer MC, infer I> ? MC extends MonomorphClass<infer P, infer iI, infer M> ? M : MC : CM[K];
43
+ [K in keyof CM as LowercaseFirstLetter<K>]?: ExtractMonomorphClass<CM[K]> extends MonomorphClass<infer P, infer I, infer M> ? M : ExtractMonomorphClass<CM[K]>;
43
44
  };
44
45
 
46
+ export declare type ComponentMapInput<CM extends ComponentMap> = Partial<{
47
+ [K in keyof CM as LowercaseFirstLetter<K>]: ComponentInput<ExtractMonomorphClass<CM[K]>>;
48
+ }>;
49
+
45
50
  export declare function createEntityClass<C>(options?: EntityCodeGenerationOptions): <CM extends ComponentMap, QM extends QueryMap<C, CM>, I extends EntityInput<CM> = EntityInput<CM>>(componentMap: CM, queries?: QM) => EntityClassWithStatics<C, CM, QM, I>;
46
51
 
47
52
  export declare type CreateEntityFunction<C, CM extends ComponentMap, I extends EntityInput<CM>> = (data?: I, pool?: EntityPoolClass<C>) => EntityInstanceWithPool<CM, I, C>;
48
53
 
49
54
  declare type ElementOfArray<T> = T extends (infer E)[] ? E : never;
50
55
 
51
- declare interface EntityBaseProperties<CM extends ComponentMap, I extends EntityInput<CM>, QM extends QueryMap<any, CM>> {
56
+ declare interface EntityBaseProperties<CM extends ComponentMap, I extends any = EntityInput<CM>> {
52
57
  componentMap: CM;
53
- addComponent: <CCK extends LowercaseFirstLetter<keyof CM>, CC extends CM[OriginalComponentKey<CCK, CM>] = CM[OriginalComponentKey<CCK, CM>]>(...a: CC extends {
54
- monomorphClass: any;
55
- } ? (undefined extends ComponentInput<CC['monomorphClass']> ? [key: CCK, data?: ComponentInput<CC['monomorphClass']>] : [key: CCK, data: ComponentInput<CC['monomorphClass']>]) : (undefined extends ComponentInput<CC> ? [key: CCK, data?: ComponentInput<CC>] : [key: CCK, data: ComponentInput<CC>])) => void;
58
+ __typescriptOnlyInputType: I;
59
+ addComponent: <CCK extends LowercaseFirstLetter<keyof CM>, CC extends CM[OriginalComponentKey<CCK, CM>] = CM[OriginalComponentKey<CCK, CM>]>(...a: (undefined extends ComponentInput<ExtractMonomorphClass<CC>> ? [key: CCK, data?: ComponentInput<ExtractMonomorphClass<CC>>] : [key: CCK, data: ComponentInput<ExtractMonomorphClass<CC>>])) => void;
56
60
  removeComponent: <CCK extends LowercaseFirstLetter<keyof CM>>(key: CCK) => void;
57
61
  hasComponent: <CCK extends LowercaseFirstLetter<keyof CM>>(key: CCK) => boolean;
58
62
  clone(): this;
@@ -60,7 +64,7 @@ declare interface EntityBaseProperties<CM extends ComponentMap, I extends Entity
60
64
  isDestroyed(): boolean;
61
65
  }
62
66
 
63
- export declare type EntityClass<CM extends ComponentMap, I extends EntityInput<CM> = EntityInput<CM>, QM extends QueryMap<any, CM> = QueryMap<any, CM>, E extends EntityInstance<CM, I, QM> = EntityInstance<CM, I, QM>> = EntityConstructor<QM, E, I, CM>;
67
+ export declare type EntityClass<CM extends ComponentMap, I extends EntityInput<CM> = EntityInput<CM>, QM extends QueryMap<any, CM> = QueryMap<any, CM>, E extends EntityInstance<CM, I> = EntityInstance<CM, I>> = EntityConstructor<QM, E, I, CM>;
64
68
 
65
69
  export declare type EntityClassWithStatics<C, CM extends ComponentMap, QM extends QueryMap<C, CM>, I extends EntityInput<CM> = EntityInput<CM>> = EntityClass<CM, I, QM> & {
66
70
  create: CreateEntityFunction<C, CM, I>;
@@ -88,26 +92,19 @@ declare type EntityCodeGenerationOptions = {
88
92
  skipSafetyChecks?: boolean;
89
93
  };
90
94
 
91
- export declare interface EntityConstructor<QM extends QueryMap<any, CM>, E extends EntityInstance<CM, I, QM>, I extends EntityInput<CM>, CM extends ComponentMap> {
95
+ export declare interface EntityConstructor<QM extends QueryMap<any, CM>, E extends EntityInstance<CM, I>, I extends any, CM extends ComponentMap> {
92
96
  new (data: I): E;
93
97
  componentMap: CM;
98
+ __typescriptOnlyInputType: I;
94
99
  pool: EntityPoolClass<E>;
95
100
  componentsWithEntities: {
96
- [K in keyof CM as CM[K] extends MonomorphClass<infer P, infer I, infer M> | {
97
- monomorphClass: MonomorphClass<infer P, infer I, infer M>;
98
- } ? LowercaseFirstLetter<K> : never]: {
99
- [Symbol.iterator](): IterableIterator<[WithPool<InstanceType<CM[K] extends MonomorphClass<any, any, any> ? CM[K] : CM[K] extends {
100
- monomorphClass: MonomorphClass<any, any, any>;
101
- } ? CM[K]['monomorphClass'] : never>>, E]>;
101
+ [K in keyof CM as ExtractMonomorphClass<CM[K]> extends MonomorphClass<infer P, infer I, infer M> ? LowercaseFirstLetter<K> : never]: {
102
+ [Symbol.iterator](): IterableIterator<[WithPool<InstanceType<ExtractMonomorphClass<CM[K]>>>, E]>;
102
103
  };
103
104
  };
104
105
  components: {
105
- [K in keyof CM as CM[K] extends MonomorphClass<infer P, infer I, infer M> | {
106
- monomorphClass: MonomorphClass<infer P, infer I, infer M>;
107
- } ? LowercaseFirstLetter<K> : never]: {
108
- [Symbol.iterator](): IterableIterator<WithPool<InstanceType<CM[K] extends MonomorphClass<any, any, any> ? CM[K] : CM[K] extends {
109
- monomorphClass: MonomorphClass<any, any, any>;
110
- } ? CM[K]['monomorphClass'] : never>>>;
106
+ [K in keyof CM as ExtractMonomorphClass<CM[K]> extends MonomorphClass<infer P, infer I, infer M> ? LowercaseFirstLetter<K> : never]: {
107
+ [Symbol.iterator](): IterableIterator<WithPool<InstanceType<ExtractMonomorphClass<CM[K]>>>>;
111
108
  };
112
109
  };
113
110
  queries: {
@@ -125,13 +122,9 @@ export declare interface EntityConstructor<QM extends QueryMap<any, CM>, E exten
125
122
  };
126
123
  }
127
124
 
128
- export declare type EntityInput<CM extends ComponentMap> = Partial<{
129
- [K in keyof CM as LowercaseFirstLetter<K>]: CM[K] extends {
130
- monomorphClass: any;
131
- } ? ComponentInput<CM[K]['monomorphClass']> : ComponentInput<CM[K]>;
132
- }>;
125
+ export declare type EntityInput<X extends ComponentMap | EntityClass<any, any, any, any> | EntityInstanceWithPool<any, any, any> | EntityInstance<any, any>> = X extends ComponentMap ? ComponentMapInput<X> : X extends EntityConstructor<infer QM, infer E, infer I, infer CM> ? I : X extends EntityInstanceWithPool<infer CM, infer I, infer E> ? I : X extends EntityInstance<infer CM, infer I> ? I : never;
133
126
 
134
- export declare type EntityInstance<CM extends ComponentMap, I extends EntityInput<CM>, QM extends QueryMap<any, CM>> = ComponentMapClassProperties<CM> & EntityBaseProperties<CM, I, QM>;
127
+ export declare type EntityInstance<CM extends ComponentMap, I extends any = EntityInput<CM>> = ComponentMapClassProperties<CM> & EntityBaseProperties<CM, I>;
135
128
 
136
129
  export declare type EntityInstanceWithPool<CM extends ComponentMap, I extends EntityInput<CM>, E> = NoInfer<E & {
137
130
  pool: EntityPoolClass<E> | null;
@@ -157,13 +150,37 @@ export declare interface EntityPoolClass<M> {
157
150
  /** how many non-destroyed objects are in this pool, i.e. how many would be iterated on */
158
151
  length: number;
159
152
  [Symbol.iterator](): IterableIterator<WithPool<M>>;
160
- create: (data?: M extends EntityInstance<infer CM, infer I, any> ? EntityInput<CM> : undefined) => M extends EntityInstanceWithPool<infer CM, infer I, any> ? EntityInstanceWithPool<CM, I, M> : never;
153
+ create(data?: M extends EntityInstance<infer CM, infer I> ? EntityInput<CM> : undefined): M extends EntityInstanceWithPool<infer CM, infer I, any> ? EntityInstanceWithPool<CM, I, M> : never;
154
+ toArray(array: NumberArray, startOffset?: number): number;
155
+ fromArray(array: NumberArray, startOffset?: number, classConstructor?: new (...args: any[]) => M): number;
156
+ fromArrayNoReferences(array: NumberArray, startOffset?: number, classConstructor?: new (...args: any[]) => M): number;
157
+ fromArrayOnlyReferences(array: NumberArray, references?: any, startOffset?: number, classConstructor?: new (...args: any[]) => M): number;
161
158
  }
162
159
 
160
+ export declare type ExtractMonomorphClass<CC> = CC extends {
161
+ monomorphClass?: infer MC extends MonomorphClass<any, any, any>;
162
+ } ? MC : CC extends ({
163
+ monomorphClass?: {
164
+ fnInArray: [() => infer MC extends MonomorphClass<any, any, any>];
165
+ };
166
+ }) ? MC : CC extends {
167
+ fnInArray: [() => infer MC extends MonomorphClass<any, any, any>];
168
+ } ? MC : CC;
169
+
163
170
  export declare function getEntityClassCode<C, CM extends ComponentMap>(componentMap: CM, queries?: QueryMap<C, CM>, options?: EntityCodeGenerationOptions): string;
164
171
 
172
+ export declare function LazyComponent<F extends () => T, T>(componentFn: F): LazyWrapper<ReturnType<F>>;
173
+
174
+ export declare type LazyMonomorphComponentConfig<MC extends MonomorphClass<any, any, any>> = ComponentConfig<LazyWrapper<MC>, InputType<InstanceType<MC>>>;
175
+
176
+ export declare type LazyWrapper<T> = {
177
+ fnInArray: [() => T];
178
+ };
179
+
165
180
  declare type LowercaseFirstLetter<S> = S extends `${infer FirstLetter}${infer Rest}` ? `${Lowercase<FirstLetter>}${Rest}` : S;
166
181
 
182
+ export declare type MonomorphComponentConfig<MC extends MonomorphClass<any, any, any>> = ComponentConfig<MC, InputType<InstanceType<MC>>> | ComponentConfig<LazyWrapper<MC>, InputType<InstanceType<MC>>>;
183
+
167
184
  declare type OriginalComponentKey<CCK extends LowercaseFirstLetter<keyof CM>, CM extends ComponentMap> = CCK extends keyof CM ? CCK : UppercaseFirstLetter<CCK>;
168
185
 
169
186
  export declare type QueryConfig<C, CM extends ComponentMap> = {
package/build/mecs.js CHANGED
@@ -1,8 +1,17 @@
1
+ function LazyComponent(componentFn) {
2
+ return { fnInArray: [componentFn] };
3
+ }
1
4
  function lowercaseFirstLetter(str) {
2
5
  return str.charAt(0).toLowerCase() + str.slice(1);
3
6
  }
4
7
  function isMonomorphClass(x) {
5
- return typeof x === "function" && "serializedSize" in x || typeof x === "object" && "monomorphClass" in x;
8
+ return typeof x === "function" && "serializedSize" in x || x instanceof Object && ("monomorphClass" in x || "fnInArray" in x);
9
+ }
10
+ function isComponentConfig(x) {
11
+ return x instanceof Object;
12
+ }
13
+ function isLazyMonomorphClass(x) {
14
+ return x instanceof Object && ("fnInArray" in x || "monomorphClass" in x && x.monomorphClass instanceof Object && "fnInArray" in x.monomorphClass);
6
15
  }
7
16
  function getEntityClassCode(componentMap, queries, options) {
8
17
  let monomorphReferenceCode = "";
@@ -51,7 +60,7 @@ function getEntityClassCode(componentMap, queries, options) {
51
60
  let processedDataCode = "data";
52
61
  let afterComponentAddedCode = "";
53
62
  let beforeComponentRemovedCode = "";
54
- if (typeof component === "object" && component !== null) {
63
+ if (isComponentConfig(component)) {
55
64
  if ("beforeEntityClassCode" in component) {
56
65
  if (typeof component.beforeEntityClassCode === "string") {
57
66
  referenceComponentFunctionsCode += component.beforeEntityClassCode;
@@ -149,15 +158,17 @@ function getEntityClassCode(componentMap, queries, options) {
149
158
  }
150
159
  }
151
160
  if (isMonomorph) {
152
- const monomorphClassName = ("monomorphClass" in component ? component.monomorphClass : component).name;
161
+ const isLazyMonomorph = isLazyMonomorphClass(component);
162
+ const monomorphClassName = isLazyMonomorph ? `LazyComponent__${componentKey}` : ("monomorphClass" in component ? component.monomorphClass : component).name;
163
+ const resolvedMonomorphClass = isLazyMonomorph ? "(" + monomorphClassName + "())" : monomorphClassName;
153
164
  if (!monomorphsAlreadyAdded.has(monomorphClassName)) {
154
165
  monomorphReferenceCode += `
155
- const ${monomorphClassName} = componentMap.${key}${"monomorphClass" in component ? ".monomorphClass" : ""};
166
+ const ${monomorphClassName} = componentMap.${key}${"monomorphClass" in component ? ".monomorphClass" : ""}${isLazyMonomorph ? ".fnInArray[0]" : ""};
156
167
  `;
157
168
  monomorphsAlreadyAdded.add(monomorphClassName);
158
169
  }
159
170
  poolCreationCode += `
160
- const ${componentKey}Pool = new ${monomorphClassName}.Pool();
171
+ const ${componentKey}Pool = new ${resolvedMonomorphClass}.Pool();
161
172
  const ${componentKey}PoolArray = ${componentKey}Pool.array;
162
173
  `;
163
174
  poolMapCreationCode += `
@@ -206,7 +217,7 @@ function getEntityClassCode(componentMap, queries, options) {
206
217
  addComponentFunctionsCode += `
207
218
  (data, entity, componentKey, updateQueryMemberships) => {
208
219
  entity.componentFlags |= ${componentFlagValue}n;
209
- entity.${componentKey} = ${monomorphClassName}.create(${processedDataCode}, ${componentKey}Pool);
220
+ entity.${componentKey} = ${resolvedMonomorphClass}.create(${processedDataCode}, ${componentKey}Pool);
210
221
  ${componentKey}ComponentToEntity[entity.${componentKey}.index] = entity;
211
222
 
212
223
  ${afterComponentAddedCode}
@@ -958,6 +969,18 @@ function getEntityClassCode(componentMap, queries, options) {
958
969
  }
959
970
  }
960
971
 
972
+ theClass.Reference = class {
973
+ constructor(reference) {
974
+ if (reference && !(reference.version & 1)) {
975
+ this.reference = reference;
976
+ this.version = reference.version;
977
+ } else {
978
+ this.reference = null;
979
+ this.version = -1;
980
+ }
981
+ }
982
+ }
983
+
961
984
  return theClass;
962
985
  `;
963
986
  if (options?.logCode) {
@@ -969,6 +992,7 @@ function createEntityClass(options) {
969
992
  return (componentMap, queries) => new Function("componentMap", "queryMap", getEntityClassCode(componentMap, queries, options))(componentMap, queries);
970
993
  }
971
994
  export {
995
+ LazyComponent,
972
996
  createEntityClass,
973
997
  getEntityClassCode
974
998
  };
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@perplexdotgg/mecs",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "MECS - Monomorph ECS - A high-performance Entity Component System for TypeScript and JavaScript projects, designed for games and simulations.",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://codeberg.org/perplexdotgg/mecs"
8
8
  },
9
+ "funding": "https://opencollective.com/perplexgg",
9
10
  "license": "MIT",
10
11
  "author": "perplex.gg",
11
12
  "type": "module",
@@ -27,7 +28,7 @@
27
28
  "bench": "vitest bench"
28
29
  },
29
30
  "peerDependencies": {
30
- "monomorph": "^1.5.0"
31
+ "monomorph": "^1.5.5"
31
32
  },
32
33
  "peerDependenciesMeta": {
33
34
  "monomorph": {
@@ -36,7 +37,7 @@
36
37
  },
37
38
  "devDependencies": {
38
39
  "@types/node": "^25.0.1",
39
- "monomorph": "^1.5.0",
40
+ "monomorph": "^1.5.5",
40
41
  "ts-node": "^10.9.2",
41
42
  "tslib": "^2.8.1",
42
43
  "typescript": "^5.9.3",