@perplexdotgg/mecs 0.1.0 → 0.3.0
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 +319 -68
- package/build/index.d.ts +95 -21
- package/build/mecs.js +226 -79
- package/package.json +11 -4
package/README.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
[](https://www.npmjs.com/package/@perplexdotgg/mecs)
|
|
2
|
+
[](https://codeberg.org/perplexdotgg/mecs)
|
|
3
|
+
[](http://codeberg.org/perplexdotgg/mecs/src/branch/main/LICENSE)
|
|
4
|
+
[](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>
|
|
@@ -5,25 +10,32 @@
|
|
|
5
10
|
|
|
6
11
|
MECS is an [ECS](https://en.wikipedia.org/wiki/Entity_component_system) library for Typescript and Javascript games and simulations, built around [Monomorph](http://codeberg.org/perplexdotgg/monomorph), a library for performant objects. The API is inspired by [Miniplex](https://github.com/hmans/miniplex), though the implementation is quite different.
|
|
7
12
|
|
|
8
|
-
|
|
13
|
+
Although using Monomorph classes as components gives you some extra features, it is not a requirement or dependency of MECS. You can use primitives (number, boolean, etc) or your own classes, and customize their lifecycle with component config objects. See the "[Non-Monomorph Components](#non-monomorph-components)" and "[Customizing Components](#customizing-components)" sections below for more details.
|
|
14
|
+
|
|
15
|
+
MECS focuses on the developer's experience in their IDE, especially around auto-complete and compile-time type-checking, while maintaining very high performance, about as high as you can achieve with an [AoS design](https://en.wikipedia.org/wiki/AoS_and_SoA).
|
|
9
16
|
|
|
10
17
|
<details>
|
|
11
|
-
<summary>Why not use SoA design?</summary>
|
|
12
|
-
While SoA can achieve better performance in the best cases, it tends to be unwieldy, especially for nested fields, and generally ECS implementations lack auto-complete for components and properties.
|
|
18
|
+
<summary>Side note: Why not use SoA design?</summary>
|
|
19
|
+
While SoA can achieve better performance in the best cases, it tends to be unwieldy, especially for nested fields, and generally ECS implementations lack auto-complete for components and properties. For larger game projecting, remembering all component names and their properties is quite difficult. Using JS getters with SoA (to achieve auto complete) gives much worse overall performance than AoS.
|
|
13
20
|
</details>
|
|
14
21
|
|
|
22
|
+
---
|
|
23
|
+
|
|
15
24
|
## Features
|
|
16
|
-
-
|
|
17
|
-
-
|
|
25
|
+
- Automatically generates highly optimized JS class code for your specific entity schema, at start up
|
|
26
|
+
- Entities and monomorph-based components automatically gain **object-pooling** features, eliminating most garbage collection ("Major GC") through automatic object re-use. Just create and destroy entities and components, and the pooling and re-use is taken care of.
|
|
27
|
+
- Simple and minimal API: There are only entities, components and queries
|
|
18
28
|
- Queries, aka archetypes, automatically match and track entities based on which components they have, or don't have
|
|
19
29
|
- Strong compile-time type safety if you use Typescript (recommended)
|
|
20
30
|
- Coming soon: automatic serialization/deserialization (to/from number arrays) of entities (fully or a subset of components) to create snapshots
|
|
21
31
|
|
|
22
|
-
MECS
|
|
32
|
+
For convenience, MECS automatically puts entities in a central pool by default, so they can be re-used. However you can also create multiple pools for an entity class, or have multiple entity classes with different schemas and pools.
|
|
23
33
|
|
|
24
|
-
MECS has no built-in concept of "systems" in the ECS sense, instead it provides queries to help you quickly write efficient code that processes specific archetypes of entities. Your systems can just be functions that iterate over entities matching a query, and your systems can also subscribe to entities newly matching, or no longer matching, a query
|
|
34
|
+
MECS has no built-in concept of "systems" in the ECS sense, instead it provides queries to help you quickly write efficient code that processes specific archetypes of entities. Your systems can just be functions that iterate over entities matching a query, and your systems can also subscribe to entities newly matching, or no longer matching, a query.
|
|
25
35
|
|
|
26
|
-
|
|
36
|
+
### When to use MECS over Monomorph alone
|
|
37
|
+
|
|
38
|
+
MECS has been created primarily around having a single entity schema/pool, where most entities only have a subset of the possible components. For example, some but not all of your entities might be visible, they may or may not have physics properties, some might be controlled by input, and so on. If all your entities have all the same components, it may be simpler to use [Monomorph](http://codeberg.org/perplexdotgg/monomorph) on its own.
|
|
27
39
|
|
|
28
40
|
## Usage
|
|
29
41
|
|
|
@@ -36,20 +48,17 @@ npm install @perplexdotgg/mecs
|
|
|
36
48
|
|
|
37
49
|
### Defining an Entity schema to create your Entity class
|
|
38
50
|
|
|
39
|
-
The following is
|
|
51
|
+
The following is JavaScript code. The Typescript version is below this.
|
|
40
52
|
|
|
41
|
-
```
|
|
53
|
+
```js
|
|
42
54
|
import { createClass } from 'monomorph';
|
|
43
|
-
import { createEntityClass
|
|
55
|
+
import { createEntityClass } from '@perplexdotgg/mecs';
|
|
44
56
|
|
|
45
57
|
// imagine some monomorph classes in your project
|
|
46
58
|
// see the monomorph docs for more on those:
|
|
47
59
|
// https://codeberg.org/perplexdotgg/monomorph
|
|
48
|
-
class Projectile extends createClass
|
|
49
|
-
class Transform extends createClass
|
|
50
|
-
|
|
51
|
-
// and some non-monomorph class
|
|
52
|
-
class MyClass {}
|
|
60
|
+
class Projectile extends createClass(projectileProps) {}
|
|
61
|
+
class Transform extends createClass(transformProps) {}
|
|
53
62
|
|
|
54
63
|
// define all the components you want entities to potentially have
|
|
55
64
|
const components = {
|
|
@@ -57,20 +66,15 @@ const components = {
|
|
|
57
66
|
localTransform: Transform,
|
|
58
67
|
globalTransform: Transform,
|
|
59
68
|
Projectile, // this is shorthand for: projectile: Projectile
|
|
69
|
+
};
|
|
60
70
|
|
|
61
|
-
|
|
62
|
-
// this syntax allows for the correct types to be expected and inferred.
|
|
63
|
-
// you are responsible for creating and managing
|
|
64
|
-
// this field, including any needed clean-up before it is removed
|
|
65
|
-
myClass: null! as VirtualComponent<MyClass>,
|
|
66
|
-
} as const;
|
|
67
|
-
|
|
71
|
+
// define queries you want to do. these always stay up-to-date, efficiently
|
|
68
72
|
const queries = {
|
|
69
73
|
projectiles: {
|
|
70
74
|
// an entity must have all of these components to match this query
|
|
71
75
|
with: ['globalTransform', 'projectile'],
|
|
72
76
|
// AND it can have none of these components to match this query
|
|
73
|
-
without: ['
|
|
77
|
+
without: ['localTransform'],
|
|
74
78
|
|
|
75
79
|
// listeners can added now, or later (see the Queries docs below)
|
|
76
80
|
afterEntityAdded: (entity) => {
|
|
@@ -79,22 +83,24 @@ const queries = {
|
|
|
79
83
|
beforeEntityRemoved: (entity) => {
|
|
80
84
|
// this is called when an entity no longer matches this query,
|
|
81
85
|
// just before a relevant component is removed from the entity
|
|
86
|
+
// and/or before an entity is destroyed
|
|
82
87
|
},
|
|
83
88
|
},
|
|
84
89
|
query2: {
|
|
85
90
|
with: ['localTransform', 'globalTransform'],
|
|
86
91
|
without: [],
|
|
87
92
|
},
|
|
88
|
-
}
|
|
93
|
+
};
|
|
89
94
|
|
|
90
|
-
|
|
95
|
+
// additional Entity class options, see "Entity Class Options" below
|
|
96
|
+
const extraOptions = {};
|
|
91
97
|
|
|
92
|
-
// automatically create our entity class from the schemas above
|
|
98
|
+
// automatically create our entity class from the schemas above.
|
|
93
99
|
// note that there are two function calls after createEntityClass
|
|
94
|
-
// to circumvent a typescript limitation,
|
|
95
|
-
class Entity extends createEntityClass
|
|
100
|
+
// to circumvent a typescript limitation (sorry, vanilla JS folks!)
|
|
101
|
+
class Entity extends createEntityClass(extraOptions)(components, queries) {
|
|
96
102
|
|
|
97
|
-
// most methods will likely exist on your components
|
|
103
|
+
// most methods will likely exist on your components rather than your entities,
|
|
98
104
|
// but you can also extend the generated entity class
|
|
99
105
|
myEntityMethod() {
|
|
100
106
|
// your code here! use 'this' like you would expect
|
|
@@ -106,20 +112,18 @@ class Entity extends createEntityClass<Entity>(debugLogging)(components, queries
|
|
|
106
112
|
```
|
|
107
113
|
|
|
108
114
|
<details>
|
|
109
|
-
<summary>
|
|
115
|
+
<summary>Typescript version</summary>
|
|
110
116
|
|
|
111
|
-
```
|
|
117
|
+
```ts
|
|
112
118
|
import { createClass } from 'monomorph';
|
|
113
|
-
import { createEntityClass } from '@perplexdotgg/mecs';
|
|
119
|
+
import { createEntityClass, type QueryMap, type ComponentMap, type ComponentConfig } from '@perplexdotgg/mecs';
|
|
114
120
|
|
|
115
|
-
// imagine some monomorph classes in your project
|
|
116
|
-
// see the monomorph docs for more on those:
|
|
121
|
+
// imagine some monomorph classes in your project, see the monomorph docs for more on those:
|
|
117
122
|
// https://codeberg.org/perplexdotgg/monomorph
|
|
118
|
-
class Projectile extends createClass(projectileProps) {}
|
|
119
|
-
class Transform extends createClass(transformProps) {}
|
|
123
|
+
class Projectile extends createClass<Projectile, typeof projectileProps>(projectileProps) {}
|
|
124
|
+
class Transform extends createClass<Transform, typeof transformProps>(transformProps) {}
|
|
120
125
|
|
|
121
|
-
//
|
|
122
|
-
class MyClass {}
|
|
126
|
+
// non-monomorph components are also supported, see "Non-Monomorph Components" below
|
|
123
127
|
|
|
124
128
|
// define all the components you want entities to potentially have
|
|
125
129
|
const components = {
|
|
@@ -127,19 +131,15 @@ const components = {
|
|
|
127
131
|
localTransform: Transform,
|
|
128
132
|
globalTransform: Transform,
|
|
129
133
|
Projectile, // this is shorthand for: projectile: Projectile
|
|
134
|
+
} as const satisfies ComponentMap;
|
|
130
135
|
|
|
131
|
-
|
|
132
|
-
// you are responsible for creating and managing
|
|
133
|
-
// this field, including any needed clean-up before it is removed
|
|
134
|
-
myClass: null,
|
|
135
|
-
};
|
|
136
|
-
|
|
136
|
+
// define queries you want to do. these always stay up-to-date, efficiently
|
|
137
137
|
const queries = {
|
|
138
138
|
projectiles: {
|
|
139
139
|
// an entity must have all of these components to match this query
|
|
140
140
|
with: ['globalTransform', 'projectile'],
|
|
141
141
|
// AND it can have none of these components to match this query
|
|
142
|
-
without: ['
|
|
142
|
+
without: ['localTransform'],
|
|
143
143
|
|
|
144
144
|
// listeners can added now, or later (see the Queries docs below)
|
|
145
145
|
afterEntityAdded: (entity) => {
|
|
@@ -154,16 +154,17 @@ const queries = {
|
|
|
154
154
|
with: ['localTransform', 'globalTransform'],
|
|
155
155
|
without: [],
|
|
156
156
|
},
|
|
157
|
-
}
|
|
157
|
+
} as const satisfies QueryMap<Entity, typeof components>;
|
|
158
158
|
|
|
159
|
-
|
|
159
|
+
// additional Entity class options, see "Entity Class Options" below
|
|
160
|
+
const extraOptions = {};
|
|
160
161
|
|
|
161
|
-
// automatically create our entity class from the schemas above
|
|
162
|
+
// automatically create our entity class from the schemas above.
|
|
162
163
|
// note that there are two function calls after createEntityClass
|
|
163
|
-
// to circumvent a typescript limitation (
|
|
164
|
-
class Entity extends createEntityClass(
|
|
164
|
+
// to circumvent a typescript limitation ([TS issue 10571](https://github.com/microsoft/TypeScript/issues/10571))
|
|
165
|
+
class Entity extends createEntityClass<Entity>(extraOptions)(components, queries) {
|
|
165
166
|
|
|
166
|
-
// most methods will likely exist on your components
|
|
167
|
+
// most methods will likely exist on your components rather than your entities,
|
|
167
168
|
// but you can also extend the generated entity class
|
|
168
169
|
myEntityMethod() {
|
|
169
170
|
// your code here! use 'this' like you would expect
|
|
@@ -180,14 +181,14 @@ Like monomorph's create method, entities accept the same object representations
|
|
|
180
181
|
|
|
181
182
|
```ts
|
|
182
183
|
const entity1 = Entity.create({
|
|
183
|
-
|
|
184
|
-
position:
|
|
185
|
-
orientation:
|
|
184
|
+
globalTransform: {
|
|
185
|
+
position: [0, 0, 0],
|
|
186
|
+
orientation: [0, 0, 0],
|
|
186
187
|
scale: 1,
|
|
187
188
|
},
|
|
188
189
|
projectile: {
|
|
189
190
|
speed: 4,
|
|
190
|
-
direction:
|
|
191
|
+
direction: [1, 0, 0],
|
|
191
192
|
},
|
|
192
193
|
});
|
|
193
194
|
|
|
@@ -200,9 +201,6 @@ const entity2 = Entity.create({
|
|
|
200
201
|
speed: 4,
|
|
201
202
|
direction: { x: 1, y: 0, z: 0 },
|
|
202
203
|
},
|
|
203
|
-
|
|
204
|
-
// you are responsible for instantiating your custom classes
|
|
205
|
-
myClass: new MyClass(),
|
|
206
204
|
});
|
|
207
205
|
|
|
208
206
|
// destroy() to return the entity to the pool, it will automatically be re-used later
|
|
@@ -210,11 +208,105 @@ const entity2 = Entity.create({
|
|
|
210
208
|
// before the component(s) needed for the query are deleted
|
|
211
209
|
// each destroyed component also gets recycled in its own component pool
|
|
212
210
|
entity2.destroy();
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Entity Class Options
|
|
214
|
+
When creating your entity class, you can pass as an optional parameter to `createEntityClass`, an object with the following properties:
|
|
215
|
+
| Option | Type | Default | Description |
|
|
216
|
+
| --- | --- | --- | --- |
|
|
217
|
+
| `skipSafetyChecks` | `boolean` | `false` | Set to `true` to skip safety checks when adding/removing components, improving performance. It is recommended to keep this `false` during development and `true` in production builds. |
|
|
218
|
+
| `logCode` | `boolean` | `false` | Set to `true` to `console.log` the code that was automatically generated for the base Entity class. |
|
|
219
|
+
| `logComponents` | `boolean` | `false` | Set to `true` to `console.log` every time a component is added or removed from an entity. (note: this affects the generated Entity code) |
|
|
220
|
+
| `logQueries` | `boolean` | `false` | Set to `true` to `console.log` every time an entity is added or removed from a query. (note: this affects the generated Entity code) |
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
### Non-Monomorph Components
|
|
224
|
+
For components that are not monomorph classes, such as your own classes or primitives like numbers and booleans,
|
|
225
|
+
you can quickly define them with a default value (such as `null`) as the component config. You are then responsible for creating and managing these components, including any necessary cleanup before they are removed. Below is a simple example; see the "[Customizing Components](#customizing-components)" section below for more advanced options.
|
|
226
|
+
|
|
227
|
+
This is the Javascript versiom, the Typescript version is below.
|
|
228
|
+
|
|
229
|
+
```js
|
|
230
|
+
// some non-monomorph class
|
|
231
|
+
class MyClass {}
|
|
232
|
+
|
|
233
|
+
const components = {
|
|
234
|
+
// allows entity.addComponent('isVisible') without specifying the second parameter, in this case
|
|
235
|
+
// entity.isVisible will be true. this is how you can use components as simple flags
|
|
236
|
+
isVisible: true,
|
|
237
|
+
|
|
238
|
+
// for non-monomorph classes, you can use a key with value null, or a default value of your choice.
|
|
239
|
+
// you are responsible for creating and managing this field, including any needed clean-up
|
|
240
|
+
// before it is removed. the `null` here is the default value that will be used when
|
|
241
|
+
// entity.addComponent('myClass') is called without a second parameter, and it is also the value set on
|
|
242
|
+
// the entity when the component is removed
|
|
243
|
+
myClass: null,
|
|
244
|
+
|
|
245
|
+
// you have much more control over the component's lifecycle, by passing a component config object,
|
|
246
|
+
// see the "Customizing Components" section below for details
|
|
247
|
+
myCustomComponent: {
|
|
248
|
+
// component config options here, see "Customizing Components" below
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
} as const satisfies ComponentMap;
|
|
252
|
+
|
|
253
|
+
const queries = {};
|
|
254
|
+
|
|
255
|
+
class Entity extends createEntityClass()(components, queries) {}
|
|
256
|
+
|
|
257
|
+
const entity = Entity.create({
|
|
258
|
+
myClass: new MyClass(),
|
|
259
|
+
isVisible: true,
|
|
260
|
+
});
|
|
261
|
+
// now entity.myClass is an instance of MyClass, and entity.isVisible is true
|
|
213
262
|
|
|
214
|
-
|
|
215
|
-
//
|
|
263
|
+
entity.destroy();
|
|
264
|
+
// now entity.myClass is null, but note that entity.isVisible is still true, based on the value in the `components` config above
|
|
216
265
|
```
|
|
217
266
|
|
|
267
|
+
<details>
|
|
268
|
+
<summary>TypeScript Version</summary>
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
// some non-monomorph class
|
|
272
|
+
class MyClass {}
|
|
273
|
+
|
|
274
|
+
const components = {
|
|
275
|
+
// allows entity.addComponent('isVisible') without specifying the second parameter, in this case
|
|
276
|
+
// entity.isVisible will be true. this is how you can use components as simple flags
|
|
277
|
+
isVisible: true as unknown as ComponentConfig<true>,
|
|
278
|
+
|
|
279
|
+
// for non-monomorph classes, you can use a key with value null, or a default value of your choice.
|
|
280
|
+
// you are responsible for creating and managing this field, including any needed clean-up
|
|
281
|
+
// before it is removed. the `null` here is the default value that will be used when
|
|
282
|
+
// entity.addComponent('myClass') is called without a second parameter, and it is also the value set on
|
|
283
|
+
// the entity when the component is removed
|
|
284
|
+
myClass: null as unknown as ComponentConfig<MyClass>,
|
|
285
|
+
|
|
286
|
+
// you have much more control over the component's lifecycle, by passing a component config object,
|
|
287
|
+
// see the "Customizing Components" section below for details
|
|
288
|
+
myCustomComponent: {
|
|
289
|
+
// config options here, see below
|
|
290
|
+
} as const satisfies ComponentConfig<TheComponentType, AcceptedInputTypes>,
|
|
291
|
+
|
|
292
|
+
} as const satisfies ComponentMap;
|
|
293
|
+
|
|
294
|
+
const queries = {};
|
|
295
|
+
|
|
296
|
+
class Entity extends createEntityClass<Entity>()(components, queries) {}
|
|
297
|
+
|
|
298
|
+
const entity = Entity.create({
|
|
299
|
+
myClass: new MyClass(),
|
|
300
|
+
isVisible: true,
|
|
301
|
+
});
|
|
302
|
+
// now entity.myClass is an instance of MyClass, and entity.isVisible is true
|
|
303
|
+
|
|
304
|
+
entity.destroy();
|
|
305
|
+
// now entity.myClass is null, and entity.isVisible is still true, based on the values in the `components` object above
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
</details>
|
|
309
|
+
|
|
218
310
|
### Accessing components and methods
|
|
219
311
|
```js
|
|
220
312
|
// calling component methods
|
|
@@ -223,6 +315,7 @@ entity1.projectile.checkCollisions();
|
|
|
223
315
|
// calling entity methods
|
|
224
316
|
entity1.myEntityMethod();
|
|
225
317
|
|
|
318
|
+
// adding amd accessing a monomorph-based component using json notation
|
|
226
319
|
entity1.addComponent('globalTransform', {
|
|
227
320
|
position: { x: 1, y: 2, z: 3, },
|
|
228
321
|
orientation: { x: 0, y: 0, z: 0, w: 1 },
|
|
@@ -232,15 +325,15 @@ entity1.globalTransform.position.x += 5;
|
|
|
232
325
|
|
|
233
326
|
// add a component, using another component as input data
|
|
234
327
|
entity2.addComponent('localTransform', entity1.localTransform);
|
|
235
|
-
// ^^ they will not be the same objects, they will just have the same data
|
|
236
|
-
// this is also true for nested objects
|
|
328
|
+
// ^^ they will not be the same objects, they will just have the same data,
|
|
329
|
+
// this is also true for nested objects. i.e:
|
|
330
|
+
// entity2.localTransform will be a deep copy of entity1.localTransform
|
|
237
331
|
|
|
238
332
|
// remove a component
|
|
239
333
|
entity1.removeComponent('projectile');
|
|
240
334
|
|
|
241
|
-
// checking if a component exists on an entity
|
|
242
|
-
if (entity1.globalTransform) { /* */ }
|
|
243
|
-
if (entity1.hasCompoonent('globalTransform')) { /* */ }
|
|
335
|
+
// checking if a component exists on an entity:
|
|
336
|
+
if (entity1.hasComponent('globalTransform')) { /* */ }
|
|
244
337
|
|
|
245
338
|
```
|
|
246
339
|
|
|
@@ -287,9 +380,167 @@ Entity.queries.projectiles.beforeEntityRemoved.removeListener(someListener);
|
|
|
287
380
|
|
|
288
381
|
```
|
|
289
382
|
|
|
383
|
+
### Components that reference Entities
|
|
384
|
+
You can have components that reference other entities, by using Monomorph's `LazyReferenceType` and `LazyReferenceListType` along with MECS's `LazyComponent`. See the Typescript tests in [`tests/lazyComponents.test.ts`](https://codeberg.org/perplexdotgg/mecs/src/branch/main/tests/lazyComponents.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: LazyComponent(() => Turret),
|
|
397
|
+
|
|
398
|
+
// alternatively, you can use a component config object for further customization:
|
|
399
|
+
turret: {
|
|
400
|
+
monomorphClass: LazyComponent(() => Turret),
|
|
401
|
+
|
|
402
|
+
// other config options here, for example:
|
|
403
|
+
afterComponentAdded: (turretInstance, entity, componentKey) => {
|
|
404
|
+
if (turretInstance.targetEntity === null) {
|
|
405
|
+
// example maybe automatically select a target in this situation
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
class Entity extends createEntityClass()(components, {}) {}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
### Customizing Components
|
|
415
|
+
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
|
|
416
|
+
[`tests/customComponents.test.ts`](https://codeberg.org/perplexdotgg/mecs/src/branch/main/tests/customComponents.test.ts).
|
|
417
|
+
|
|
418
|
+
This is the Javascript version, the Typescript version is below.
|
|
419
|
+
|
|
420
|
+
```js
|
|
421
|
+
class MyClass {}
|
|
422
|
+
|
|
423
|
+
const components = {
|
|
424
|
+
myClass: {
|
|
425
|
+
// called after the component is added to an entity
|
|
426
|
+
afterComponentAdded: (myClassInstance, entity, componentKey) => {
|
|
427
|
+
// do any additional actions on myClassInstance here
|
|
428
|
+
// componentKey will be the string 'myClass' in this case
|
|
429
|
+
},
|
|
430
|
+
|
|
431
|
+
// called before the component is removed from an entity
|
|
432
|
+
beforeComponentRemoved: (myClassInstance, entity, componentKey) => {
|
|
433
|
+
// do any needed cleanup on myClassInstance here
|
|
434
|
+
},
|
|
435
|
+
|
|
436
|
+
// this function will be called to process the input data when adding this component
|
|
437
|
+
processDataOnAdd: (data) => {
|
|
438
|
+
if (data === null) {
|
|
439
|
+
// provide a default value if needed
|
|
440
|
+
data = new MyClass();
|
|
441
|
+
}
|
|
442
|
+
// modify and return the data as needed
|
|
443
|
+
return data;
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
|
|
447
|
+
myCustomMonomorphComponent: {
|
|
448
|
+
monomorphClass: SomeMonomorphClass,
|
|
449
|
+
|
|
450
|
+
// all other config options are available, see the tests for more examples
|
|
451
|
+
},
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
const queries = {};
|
|
455
|
+
|
|
456
|
+
class Entity extends createEntityClass()(components, queries) {}
|
|
457
|
+
|
|
458
|
+
const entity = Entity.create({
|
|
459
|
+
myClass: null, // this will automatically be processed by processDataOnAdd
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// entity.myClass is now an instance of MyClass
|
|
463
|
+
|
|
464
|
+
const entity2 = Entity.create({
|
|
465
|
+
myClass: new MyClass(), // this still works, because of the (data === null) check in processDataOnAdd
|
|
466
|
+
});
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
<details>
|
|
470
|
+
<summary>TypeScript Version</summary>
|
|
471
|
+
|
|
472
|
+
```ts
|
|
473
|
+
class MyClass {}
|
|
474
|
+
|
|
475
|
+
const components = {
|
|
476
|
+
myClass: {
|
|
477
|
+
// called after the component is added to an entity
|
|
478
|
+
afterComponentAdded: (myClassInstance, entity, componentKey) => {
|
|
479
|
+
// do any additional actions on myClassInstance here
|
|
480
|
+
// componentKey will be the string 'myClass' in this case
|
|
481
|
+
},
|
|
482
|
+
|
|
483
|
+
// called before the component is removed from an entity
|
|
484
|
+
beforeComponentRemoved: (myClassInstance, entity, componentKey) => {
|
|
485
|
+
// do any needed cleanup on myClassInstance here
|
|
486
|
+
},
|
|
487
|
+
|
|
488
|
+
// this function will be called to process the input data when adding this component
|
|
489
|
+
processDataOnAdd: (data) => {
|
|
490
|
+
if (data === null) {
|
|
491
|
+
// provide a default value if needed
|
|
492
|
+
data = new MyClass();
|
|
493
|
+
}
|
|
494
|
+
// modify and return the data as needed
|
|
495
|
+
return data;
|
|
496
|
+
},
|
|
497
|
+
// ComponentConfig<MyClass, MyClass | null> means that entity.myClass will be of type MyClass,
|
|
498
|
+
// and when adding the component, the input data can be either MyClass or null
|
|
499
|
+
} as const satisfies ComponentConfig<MyClass, MyClass | null>,
|
|
500
|
+
|
|
501
|
+
myCustomMonomorphComponent: {
|
|
502
|
+
monomorphClass: SomeMonomorphClass,
|
|
503
|
+
|
|
504
|
+
// all other config options are available, see the tests for more examples
|
|
505
|
+
} as const satisfies ComponentConfig<SomeMonomorphClass>,
|
|
506
|
+
} as const satisfies ComponentMap;
|
|
507
|
+
|
|
508
|
+
const queries = {};
|
|
509
|
+
|
|
510
|
+
class Entity extends createEntityClass<Entity>()(components, queries) {}
|
|
511
|
+
|
|
512
|
+
const entity = Entity.create({
|
|
513
|
+
myClass: null, // this will automatically be processed by processDataOnAdd
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// entity.myClass is now an instance of MyClass
|
|
517
|
+
|
|
518
|
+
const entity2 = Entity.create({
|
|
519
|
+
myClass: new MyClass(), // this still works
|
|
520
|
+
});
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
</details>
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
Here are all the available component config options:
|
|
528
|
+
|
|
529
|
+
| Option | Type | Description |
|
|
530
|
+
| --- | --- | --- |
|
|
531
|
+
| `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. |
|
|
532
|
+
| `afterComponentAdded` | Function: `(componentInstance, entity, componentKey) => void` | A function that will be called after the component is added. This will happen before any query membership-related triggers occur. |
|
|
533
|
+
| `afterComponentAddedCode` | String or Function that returns a string | Like the above, but you provide the code that will be injected directly into the generated Entity code for this component. See the [tests](https://codeberg.org/perplexdotgg/mecs/src/branch/main/tests/customComponents.test.ts) for more details. |
|
|
534
|
+
| `beforeComponentRemoved` | Function: `(componentInstance, entity, componentKey) => void` | A function that will be called before the component is removed. This will happen after any query membership-related triggers occur. |
|
|
535
|
+
| `beforeComponentRemovedCode` | String or Function that returns a string | Like the above, but you provide the code that will be injected directly into the generated Entity code for this component. See the [tests](https://codeberg.org/perplexdotgg/mecs/src/branch/main/tests/customComponents.test.ts) for more details. |
|
|
536
|
+
| `processDataOnAdd` | Function: `(data) => processedData` | A function that will be called to process the input data when adding this component. The returned value will be used as the component's value for non-monomorph components. For monomorph components, the processed value will be passed to the class's create method. |
|
|
537
|
+
| `processDataOnAddCode` | String or Function that returns a string | Like the above, but you provide the code that will be injected directly into the generated Entity code for this component. See the [tests](https://codeberg.org/perplexdotgg/mecs/src/branch/main/tests/customComponents.test.ts) for more details. |
|
|
538
|
+
| `nullComponent` | Function: `(entity, componentKey) => void` | By default, i.e. when this option is not specified, the component property on the entity gets set to `null` when the component is removed from the entity. To override that behavior, you can provide a function that does whatever you prefer (pass a noop function to leave the value as is) |
|
|
539
|
+
| `nullComponentCode` | String or Function that returns a string | Like the above, but you provide the code that will be injected directly into the generated Entity code for this component. See the [tests](https://codeberg.org/perplexdotgg/mecs/src/branch/main/tests/customComponents.test.ts) for more details. |
|
|
540
|
+
| `beforeEntityClassCode` | String or Function that returns a string | Code that will be injected at the start of the generated code, before the Entity class's code. Useful for defining helper functions or values needed by all the options above. See the [tests](https://codeberg.org/perplexdotgg/mecs/src/branch/main/tests/customComponents.test.ts) for more details. |
|
|
541
|
+
|
|
290
542
|
## Roadmap
|
|
291
543
|
|
|
292
|
-
- Integrating the upcoming version of monomorph where non-monomorth classes are better supported (e.g. to allow a component to contain a reference to an html element)
|
|
293
544
|
- Example project/template coming soon
|
|
294
545
|
- Basic benchmarks
|
|
295
546
|
- Entity/component data debugging tool(s)
|
|
@@ -297,4 +548,4 @@ Entity.queries.projectiles.beforeEntityRemoved.removeListener(someListener);
|
|
|
297
548
|
|
|
298
549
|
## How to contribute
|
|
299
550
|
|
|
300
|
-
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!
|
|
551
|
+
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,54 +1,106 @@
|
|
|
1
1
|
import { InputType } from 'monomorph';
|
|
2
2
|
import { MonomorphClass } from 'monomorph';
|
|
3
3
|
import { MonomorphInstance } from 'monomorph';
|
|
4
|
-
import {
|
|
4
|
+
import { NumberArray } from 'monomorph';
|
|
5
5
|
import { WithPool } from 'monomorph';
|
|
6
6
|
|
|
7
|
-
export declare type
|
|
7
|
+
export declare type ComponentConfig<ComponentType, ComponentInputType extends any = InputType<ComponentType> extends never ? ComponentType : InputType<ComponentType>> = {
|
|
8
|
+
monomorphClass?: ComponentType extends MonomorphClass<any, any, any> ? ComponentType : ComponentType extends MonomorphInstance<infer P, infer I> ? MonomorphClass<P, I, ComponentType> : never;
|
|
9
|
+
afterComponentAdded?: (componentInstance: ComponentType extends abstract new (...args: any) => any ? InstanceType<ComponentType> : ComponentType, entity: any, componentKey: string) => void;
|
|
10
|
+
afterComponentAddedCode?: string | ComponentConfigFunction<ComponentType, ComponentInputType>;
|
|
11
|
+
beforeComponentRemoved?: (componentInstance: ComponentType extends abstract new (...args: any) => any ? InstanceType<ComponentType> : ComponentType, entity: any, componentKey: string) => void;
|
|
12
|
+
beforeComponentRemovedCode?: string | ComponentConfigFunction<ComponentType, ComponentInputType>;
|
|
13
|
+
processDataOnAdd?: (data: ComponentInputType) => ComponentInputType;
|
|
14
|
+
processDataOnAddCode?: string | ((options: {
|
|
15
|
+
dataVariableName: string;
|
|
16
|
+
componentConfigReference: string;
|
|
17
|
+
}) => string);
|
|
18
|
+
nullComponent?: (entity: any, componentKey: string) => void;
|
|
19
|
+
nullComponentCode?: string | ComponentConfigFunction<ComponentType, ComponentInputType>;
|
|
20
|
+
beforeEntityClassCode?: string | ComponentConfigBeforeEntityClassFunction<ComponentType, ComponentInputType>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export declare type ComponentConfigBeforeEntityClassFunction<ComponentType, ComponentInputType> = ((options: {
|
|
24
|
+
componentKey: string;
|
|
25
|
+
componentFlagValue: bigint;
|
|
26
|
+
componentIndex: number;
|
|
27
|
+
componentConfig: ComponentConfig<ComponentType, ComponentInputType>;
|
|
28
|
+
}) => string);
|
|
29
|
+
|
|
30
|
+
export declare type ComponentConfigFunction<ComponentType, ComponentInputType> = ((options: {
|
|
31
|
+
entityVariableName: string;
|
|
32
|
+
componentKey: string;
|
|
33
|
+
componentFlagValue: bigint;
|
|
34
|
+
componentIndex: number;
|
|
35
|
+
componentConfig: ComponentConfig<ComponentType, ComponentInputType>;
|
|
36
|
+
}) => string);
|
|
8
37
|
|
|
9
|
-
export declare type
|
|
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;
|
|
39
|
+
|
|
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>>;
|
|
10
41
|
|
|
11
42
|
declare type ComponentMapClassProperties<CM> = {
|
|
12
|
-
[K in keyof CM as LowercaseFirstLetter<K>]?: CM[K] extends MonomorphClass<infer P, infer I, infer M> ? M : 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]>;
|
|
13
44
|
};
|
|
14
45
|
|
|
15
|
-
export declare function createEntityClass<C>(
|
|
46
|
+
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>;
|
|
16
47
|
|
|
17
48
|
export declare type CreateEntityFunction<C, CM extends ComponentMap, I extends EntityInput<CM>> = (data?: I, pool?: EntityPoolClass<C>) => EntityInstanceWithPool<CM, I, C>;
|
|
18
49
|
|
|
19
50
|
declare type ElementOfArray<T> = T extends (infer E)[] ? E : never;
|
|
20
51
|
|
|
21
|
-
declare interface EntityBaseProperties<CM extends ComponentMap, I extends EntityInput<CM
|
|
52
|
+
declare interface EntityBaseProperties<CM extends ComponentMap, I extends EntityInput<CM>> {
|
|
22
53
|
componentMap: CM;
|
|
23
|
-
|
|
54
|
+
__typescriptOnlyInputType: I;
|
|
55
|
+
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;
|
|
24
56
|
removeComponent: <CCK extends LowercaseFirstLetter<keyof CM>>(key: CCK) => void;
|
|
25
57
|
hasComponent: <CCK extends LowercaseFirstLetter<keyof CM>>(key: CCK) => boolean;
|
|
26
58
|
clone(): this;
|
|
27
|
-
copy(other: this): this;
|
|
28
|
-
set(data: PartialRecursive<I>): this;
|
|
29
59
|
destroy(): void;
|
|
30
60
|
isDestroyed(): boolean;
|
|
31
61
|
}
|
|
32
62
|
|
|
33
|
-
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
|
|
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> = EntityInstance<CM, I>> = EntityConstructor<QM, E, I, CM>;
|
|
34
64
|
|
|
35
65
|
export declare type EntityClassWithStatics<C, CM extends ComponentMap, QM extends QueryMap<C, CM>, I extends EntityInput<CM> = EntityInput<CM>> = EntityClass<CM, I, QM> & {
|
|
36
66
|
create: CreateEntityFunction<C, CM, I>;
|
|
37
67
|
Pool: NoInfer<EntityPoolClass<C>>;
|
|
38
68
|
};
|
|
39
69
|
|
|
40
|
-
|
|
70
|
+
declare type EntityCodeGenerationOptions = {
|
|
71
|
+
/**
|
|
72
|
+
* If true, logs all the generated class code for the entity class
|
|
73
|
+
*/
|
|
74
|
+
logCode?: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* If true, logs when components are added to or removed from entities
|
|
77
|
+
*/
|
|
78
|
+
logComponents?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* If true, logs when entities are added to or removed from queries
|
|
81
|
+
*/
|
|
82
|
+
logQueries?: boolean;
|
|
83
|
+
/**
|
|
84
|
+
* If true, skips safety checks such as trying to add a component that an entity already has
|
|
85
|
+
* It is recommended that you set to true in production to improve performance, while leave false
|
|
86
|
+
* during development to catch unintended mistakes, or missing hasComponent() checks in your code
|
|
87
|
+
*/
|
|
88
|
+
skipSafetyChecks?: boolean;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export declare interface EntityConstructor<QM extends QueryMap<any, CM>, E extends EntityInstance<CM, I>, I extends EntityInput<CM>, CM extends ComponentMap> {
|
|
41
92
|
new (data: I): E;
|
|
42
93
|
componentMap: CM;
|
|
94
|
+
__typescriptOnlyInputType: I;
|
|
43
95
|
pool: EntityPoolClass<E>;
|
|
44
96
|
componentsWithEntities: {
|
|
45
|
-
[K in keyof CM as CM[K] extends MonomorphClass<infer P, infer I, infer M> ? LowercaseFirstLetter<K> : never]: {
|
|
46
|
-
[Symbol.iterator](): IterableIterator<[WithPool<InstanceType<CM[K]
|
|
97
|
+
[K in keyof CM as ExtractMonomorphClass<CM[K]> extends MonomorphClass<infer P, infer I, infer M> ? LowercaseFirstLetter<K> : never]: {
|
|
98
|
+
[Symbol.iterator](): IterableIterator<[WithPool<InstanceType<ExtractMonomorphClass<CM[K]>>>, E]>;
|
|
47
99
|
};
|
|
48
100
|
};
|
|
49
101
|
components: {
|
|
50
|
-
[K in keyof CM as CM[K] extends MonomorphClass<infer P, infer I, infer M> ? LowercaseFirstLetter<K> : never]: {
|
|
51
|
-
[Symbol.iterator](): IterableIterator<WithPool<InstanceType<CM[K]
|
|
102
|
+
[K in keyof CM as ExtractMonomorphClass<CM[K]> extends MonomorphClass<infer P, infer I, infer M> ? LowercaseFirstLetter<K> : never]: {
|
|
103
|
+
[Symbol.iterator](): IterableIterator<WithPool<InstanceType<ExtractMonomorphClass<CM[K]>>>>;
|
|
52
104
|
};
|
|
53
105
|
};
|
|
54
106
|
queries: {
|
|
@@ -67,10 +119,10 @@ export declare interface EntityConstructor<QM extends QueryMap<any, CM>, E exten
|
|
|
67
119
|
}
|
|
68
120
|
|
|
69
121
|
export declare type EntityInput<CM extends ComponentMap> = Partial<{
|
|
70
|
-
[K in keyof CM as LowercaseFirstLetter<K>]: ComponentInput<CM[K]
|
|
122
|
+
[K in keyof CM as LowercaseFirstLetter<K>]: ComponentInput<ExtractMonomorphClass<CM[K]>>;
|
|
71
123
|
}>;
|
|
72
124
|
|
|
73
|
-
export declare type EntityInstance<CM extends ComponentMap, I extends EntityInput<CM
|
|
125
|
+
export declare type EntityInstance<CM extends ComponentMap, I extends EntityInput<CM>> = ComponentMapClassProperties<CM> & EntityBaseProperties<CM, I>;
|
|
74
126
|
|
|
75
127
|
export declare type EntityInstanceWithPool<CM extends ComponentMap, I extends EntityInput<CM>, E> = NoInfer<E & {
|
|
76
128
|
pool: EntityPoolClass<E> | null;
|
|
@@ -96,13 +148,37 @@ export declare interface EntityPoolClass<M> {
|
|
|
96
148
|
/** how many non-destroyed objects are in this pool, i.e. how many would be iterated on */
|
|
97
149
|
length: number;
|
|
98
150
|
[Symbol.iterator](): IterableIterator<WithPool<M>>;
|
|
99
|
-
create
|
|
151
|
+
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;
|
|
152
|
+
toArray(array: NumberArray, startOffset?: number): number;
|
|
153
|
+
fromArray(array: NumberArray, startOffset?: number, classConstructor?: new (...args: any[]) => M): number;
|
|
154
|
+
fromArrayNoReferences(array: NumberArray, startOffset?: number, classConstructor?: new (...args: any[]) => M): number;
|
|
155
|
+
fromArrayOnlyReferences(array: NumberArray, references?: any, startOffset?: number, classConstructor?: new (...args: any[]) => M): number;
|
|
100
156
|
}
|
|
101
157
|
|
|
102
|
-
export declare
|
|
158
|
+
export declare type ExtractMonomorphClass<CC> = CC extends {
|
|
159
|
+
monomorphClass?: infer MC extends MonomorphClass<any, any, any>;
|
|
160
|
+
} ? MC : CC extends ({
|
|
161
|
+
monomorphClass?: {
|
|
162
|
+
fnInArray: [() => infer MC extends MonomorphClass<any, any, any>];
|
|
163
|
+
};
|
|
164
|
+
}) ? MC : CC extends {
|
|
165
|
+
fnInArray: [() => infer MC extends MonomorphClass<any, any, any>];
|
|
166
|
+
} ? MC : CC;
|
|
167
|
+
|
|
168
|
+
export declare function getEntityClassCode<C, CM extends ComponentMap>(componentMap: CM, queries?: QueryMap<C, CM>, options?: EntityCodeGenerationOptions): string;
|
|
169
|
+
|
|
170
|
+
export declare function LazyComponent<F extends () => T, T>(componentFn: F): LazyWrapper<ReturnType<F>>;
|
|
171
|
+
|
|
172
|
+
export declare type LazyMonomorphComponentConfig<MC extends MonomorphClass<any, any, any>> = ComponentConfig<LazyWrapper<MC>, InputType<InstanceType<MC>>>;
|
|
173
|
+
|
|
174
|
+
export declare type LazyWrapper<T> = {
|
|
175
|
+
fnInArray: [() => T];
|
|
176
|
+
};
|
|
103
177
|
|
|
104
178
|
declare type LowercaseFirstLetter<S> = S extends `${infer FirstLetter}${infer Rest}` ? `${Lowercase<FirstLetter>}${Rest}` : S;
|
|
105
179
|
|
|
180
|
+
export declare type MonomorphComponentConfig<MC extends MonomorphClass<any, any, any>> = ComponentConfig<MC, InputType<InstanceType<MC>>> | ComponentConfig<LazyWrapper<MC>, InputType<InstanceType<MC>>>;
|
|
181
|
+
|
|
106
182
|
declare type OriginalComponentKey<CCK extends LowercaseFirstLetter<keyof CM>, CM extends ComponentMap> = CCK extends keyof CM ? CCK : UppercaseFirstLetter<CCK>;
|
|
107
183
|
|
|
108
184
|
export declare type QueryConfig<C, CM extends ComponentMap> = {
|
|
@@ -116,8 +192,6 @@ export declare type QueryMap<C, CM extends ComponentMap> = Record<string, QueryC
|
|
|
116
192
|
|
|
117
193
|
declare type UppercaseFirstLetter<S> = S extends `${infer FirstLetter}${infer Rest}` ? `${Uppercase<FirstLetter>}${Rest}` : S;
|
|
118
194
|
|
|
119
|
-
export declare type VirtualComponent<T> = T;
|
|
120
|
-
|
|
121
195
|
export declare type WithComponent<C extends {
|
|
122
196
|
componentMap: any;
|
|
123
197
|
}, KCM extends LowercaseFirstLetter<keyof C['componentMap']>> = Writeable<Omit<C, KCM> & {
|
package/build/mecs.js
CHANGED
|
@@ -1,22 +1,33 @@
|
|
|
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;
|
|
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
|
-
function getEntityClassCode(componentMap, queries,
|
|
16
|
+
function getEntityClassCode(componentMap, queries, options) {
|
|
8
17
|
let monomorphReferenceCode = "";
|
|
9
18
|
let poolCreationCode = "";
|
|
10
19
|
let poolMapCreationCode = "const componentPools = {\n";
|
|
11
20
|
let componentToEntityCode = "";
|
|
21
|
+
let referenceComponentFunctionsCode = "";
|
|
12
22
|
let componentIndicesCode = "";
|
|
13
23
|
let addComponentFunctionsCode = "";
|
|
14
24
|
let removeComponentFunctionsCode = "";
|
|
25
|
+
let nullComponentCode = "";
|
|
15
26
|
let componentFlagsCode = "";
|
|
16
27
|
let createFromDataCode = "";
|
|
17
28
|
let createFromDataCodeAllNulls = "";
|
|
18
29
|
let destroyCode = "";
|
|
19
|
-
let
|
|
30
|
+
let componentWithEntityIteratorFunctionsCode = "";
|
|
20
31
|
const componentFlags = [];
|
|
21
32
|
const componentIndices = {};
|
|
22
33
|
const monomorphsAlreadyAdded = /* @__PURE__ */ new Set();
|
|
@@ -27,7 +38,7 @@ function getEntityClassCode(componentMap, queries, debug) {
|
|
|
27
38
|
const isMonomorph = isMonomorphClass(component);
|
|
28
39
|
componentIndices[componentKey] = componentIndex;
|
|
29
40
|
componentIndicesCode += `
|
|
30
|
-
${componentKey}
|
|
41
|
+
['${componentKey}', ${componentIndex}],
|
|
31
42
|
`;
|
|
32
43
|
componentFlags.push(componentFlagValue);
|
|
33
44
|
componentFlagsCode += `${componentFlagValue}n, `;
|
|
@@ -41,43 +52,133 @@ function getEntityClassCode(componentMap, queries, debug) {
|
|
|
41
52
|
createFromDataCodeAllNulls += `
|
|
42
53
|
this.${componentKey} = null;
|
|
43
54
|
`;
|
|
55
|
+
destroyCode += `
|
|
56
|
+
if ((thisComponentFlags & ${componentFlagValue}n) !== 0n) {
|
|
57
|
+
this.removeComponent('${componentKey}', updateQueryMemberships);
|
|
58
|
+
}
|
|
59
|
+
`;
|
|
60
|
+
let processedDataCode = "data";
|
|
61
|
+
let afterComponentAddedCode = "";
|
|
62
|
+
let beforeComponentRemovedCode = "";
|
|
63
|
+
if (isComponentConfig(component)) {
|
|
64
|
+
if ("beforeEntityClassCode" in component) {
|
|
65
|
+
if (typeof component.beforeEntityClassCode === "string") {
|
|
66
|
+
referenceComponentFunctionsCode += component.beforeEntityClassCode;
|
|
67
|
+
} else {
|
|
68
|
+
referenceComponentFunctionsCode += component.beforeEntityClassCode({
|
|
69
|
+
componentKey,
|
|
70
|
+
componentFlagValue,
|
|
71
|
+
componentIndex,
|
|
72
|
+
componentConfig: component
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if ("afterComponentAdded" in component) {
|
|
77
|
+
referenceComponentFunctionsCode += `
|
|
78
|
+
const ${componentKey}OnAdded = componentMap.${key}.afterComponentAdded;
|
|
79
|
+
`;
|
|
80
|
+
afterComponentAddedCode = `
|
|
81
|
+
${componentKey}OnAdded(entity.${componentKey}, entity, '${componentKey}');
|
|
82
|
+
`;
|
|
83
|
+
} else if ("afterComponentAddedCode" in component) {
|
|
84
|
+
if (typeof component.afterComponentAddedCode === "string") {
|
|
85
|
+
afterComponentAddedCode = component.afterComponentAddedCode;
|
|
86
|
+
} else {
|
|
87
|
+
afterComponentAddedCode = component.afterComponentAddedCode({
|
|
88
|
+
entityVariableName: "entity",
|
|
89
|
+
componentKey,
|
|
90
|
+
componentFlagValue,
|
|
91
|
+
componentIndex,
|
|
92
|
+
componentConfig: component
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if ("beforeComponentRemoved" in component) {
|
|
97
|
+
referenceComponentFunctionsCode += `
|
|
98
|
+
const ${componentKey}OnRemoved = componentMap.${key}.beforeComponentRemoved;
|
|
99
|
+
`;
|
|
100
|
+
beforeComponentRemovedCode = `
|
|
101
|
+
${componentKey}OnRemoved(entity.${componentKey}, entity, '${componentKey}');
|
|
102
|
+
`;
|
|
103
|
+
} else if ("beforeComponentRemovedCode" in component) {
|
|
104
|
+
if (typeof component.beforeComponentRemovedCode === "string") {
|
|
105
|
+
beforeComponentRemovedCode = component.beforeComponentRemovedCode;
|
|
106
|
+
} else {
|
|
107
|
+
beforeComponentRemovedCode = component.beforeComponentRemovedCode({
|
|
108
|
+
entityVariableName: "entity",
|
|
109
|
+
componentKey,
|
|
110
|
+
componentFlagValue,
|
|
111
|
+
componentIndex,
|
|
112
|
+
componentConfig: component
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if ("processDataOnAdd" in component) {
|
|
117
|
+
referenceComponentFunctionsCode += `
|
|
118
|
+
const ${componentKey}ProcessDataOnAdd = componentMap.${key}.processDataOnAdd;
|
|
119
|
+
`;
|
|
120
|
+
processedDataCode = `${componentKey}ProcessDataOnAdd(data)`;
|
|
121
|
+
} else if ("processDataOnAddCode" in component) {
|
|
122
|
+
if (typeof component.processDataOnAddCode === "string") {
|
|
123
|
+
processedDataCode = component.processDataOnAddCode;
|
|
124
|
+
} else {
|
|
125
|
+
processedDataCode = component.processDataOnAddCode({ dataVariableName: "data", componentConfigReference: "componentMap." + key });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if ("nullComponent" in component) {
|
|
129
|
+
referenceComponentFunctionsCode += `
|
|
130
|
+
const ${componentKey}NullComponent = componentMap.${key}.nullComponent;
|
|
131
|
+
`;
|
|
132
|
+
nullComponentCode = `
|
|
133
|
+
${componentKey}NullComponent(entity, '${componentKey}');
|
|
134
|
+
`;
|
|
135
|
+
} else if ("nullComponentCode" in component) {
|
|
136
|
+
if (typeof component.nullComponentCode === "string") {
|
|
137
|
+
nullComponentCode = component.nullComponentCode;
|
|
138
|
+
} else {
|
|
139
|
+
nullComponentCode = component.nullComponentCode({
|
|
140
|
+
entityVariableName: "entity",
|
|
141
|
+
componentKey,
|
|
142
|
+
componentFlagValue,
|
|
143
|
+
componentIndex,
|
|
144
|
+
componentConfig: component
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
nullComponentCode = `
|
|
149
|
+
entity.${componentKey} = null;
|
|
150
|
+
`;
|
|
151
|
+
}
|
|
152
|
+
} else if (!isMonomorph) {
|
|
153
|
+
processedDataCode = "data ?? componentMap." + key;
|
|
154
|
+
if (typeof component === "number" || typeof component === "string" || typeof component === "boolean" || component === null || component === void 0) {
|
|
155
|
+
nullComponentCode = `
|
|
156
|
+
entity.${componentKey} = ${component === void 0 ? "undefined" : JSON.stringify(component)};
|
|
157
|
+
`;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
44
160
|
if (isMonomorph) {
|
|
45
|
-
|
|
161
|
+
const isLazyMonomorph = isLazyMonomorphClass(component);
|
|
162
|
+
const monomorphClassName = isLazyMonomorph ? `LazyComponent__${componentKey}` : ("monomorphClass" in component ? component.monomorphClass : component).name;
|
|
163
|
+
const resolvedMonomorphClass = isLazyMonomorph ? "(" + monomorphClassName + "())" : monomorphClassName;
|
|
164
|
+
if (!monomorphsAlreadyAdded.has(monomorphClassName)) {
|
|
46
165
|
monomorphReferenceCode += `
|
|
47
|
-
const ${
|
|
166
|
+
const ${monomorphClassName} = componentMap.${key}${"monomorphClass" in component ? ".monomorphClass" : ""}${isLazyMonomorph ? ".fnInArray[0]" : ""};
|
|
48
167
|
`;
|
|
49
|
-
monomorphsAlreadyAdded.add(
|
|
168
|
+
monomorphsAlreadyAdded.add(monomorphClassName);
|
|
50
169
|
}
|
|
51
170
|
poolCreationCode += `
|
|
52
|
-
const ${componentKey}Pool = new ${
|
|
171
|
+
const ${componentKey}Pool = new ${resolvedMonomorphClass}.Pool();
|
|
53
172
|
const ${componentKey}PoolArray = ${componentKey}Pool.array;
|
|
54
173
|
`;
|
|
55
174
|
poolMapCreationCode += `
|
|
56
175
|
${componentKey}: ${componentKey}Pool,
|
|
57
176
|
`;
|
|
58
|
-
addComponentFunctionsCode += `
|
|
59
|
-
(data, entity) => {
|
|
60
|
-
const instance = ${component.name}.create(data, ${componentKey}Pool);
|
|
61
|
-
${componentKey}ComponentToEntity[instance.index] = entity;
|
|
62
|
-
return instance;
|
|
63
|
-
},
|
|
64
|
-
`;
|
|
65
|
-
removeComponentFunctionsCode += `
|
|
66
|
-
instance => {
|
|
67
|
-
${componentKey}ComponentToEntity[instance.index] = null;
|
|
68
|
-
instance.destroy();
|
|
69
|
-
},
|
|
70
|
-
`;
|
|
71
|
-
destroyCode += `
|
|
72
|
-
if ((this.componentFlags & ${componentFlagValue}n) !== 0n) {
|
|
73
|
-
this.removeComponent('${componentKey}', updateQueryMemberships);
|
|
74
|
-
}
|
|
75
|
-
`;
|
|
76
177
|
componentToEntityCode += `
|
|
77
178
|
const ${componentKey}ComponentToEntity = [null];
|
|
78
179
|
${componentKey}ComponentToEntity.pop();
|
|
79
180
|
`;
|
|
80
|
-
|
|
181
|
+
componentWithEntityIteratorFunctionsCode += `
|
|
81
182
|
${componentKey}: {
|
|
82
183
|
[Symbol.iterator]() {
|
|
83
184
|
const resultArray = [null, null];
|
|
@@ -113,12 +214,60 @@ function getEntityClassCode(componentMap, queries, debug) {
|
|
|
113
214
|
}
|
|
114
215
|
},
|
|
115
216
|
`;
|
|
217
|
+
addComponentFunctionsCode += `
|
|
218
|
+
(data, entity, componentKey, updateQueryMemberships) => {
|
|
219
|
+
entity.componentFlags |= ${componentFlagValue}n;
|
|
220
|
+
entity.${componentKey} = ${resolvedMonomorphClass}.create(${processedDataCode}, ${componentKey}Pool);
|
|
221
|
+
${componentKey}ComponentToEntity[entity.${componentKey}.index] = entity;
|
|
222
|
+
|
|
223
|
+
${afterComponentAddedCode}
|
|
224
|
+
|
|
225
|
+
if (updateQueryMemberships) {
|
|
226
|
+
entity.updateQueryMemberships();
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
`;
|
|
230
|
+
removeComponentFunctionsCode += `
|
|
231
|
+
(instance, entity, componentKey, updateQueryMemberships) => {
|
|
232
|
+
const newFlagValue = entity.componentFlags & ~${componentFlagValue}n;
|
|
233
|
+
if (updateQueryMemberships) {
|
|
234
|
+
entity.updateQueryMemberships(newFlagValue);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
${beforeComponentRemovedCode}
|
|
238
|
+
|
|
239
|
+
${componentKey}ComponentToEntity[instance.index] = null;
|
|
240
|
+
instance.destroy();
|
|
241
|
+
|
|
242
|
+
entity.componentFlags = newFlagValue;
|
|
243
|
+
${nullComponentCode};
|
|
244
|
+
},
|
|
245
|
+
`;
|
|
116
246
|
} else {
|
|
117
247
|
addComponentFunctionsCode += `
|
|
118
|
-
|
|
248
|
+
(data, entity, componentKey, updateQueryMemberships) => {
|
|
249
|
+
entity.componentFlags |= ${componentFlagValue}n;
|
|
250
|
+
entity.${componentKey} = ${processedDataCode};
|
|
251
|
+
|
|
252
|
+
${afterComponentAddedCode}
|
|
253
|
+
|
|
254
|
+
if (updateQueryMemberships) {
|
|
255
|
+
entity.updateQueryMemberships();
|
|
256
|
+
}
|
|
257
|
+
},
|
|
119
258
|
`;
|
|
120
259
|
removeComponentFunctionsCode += `
|
|
121
|
-
|
|
260
|
+
(currentComponentValue, entity, componentKey, updateQueryMemberships) => {
|
|
261
|
+
const newFlagValue = entity.componentFlags & ~${componentFlagValue}n;
|
|
262
|
+
if (updateQueryMemberships) {
|
|
263
|
+
entity.updateQueryMemberships(newFlagValue);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
${beforeComponentRemovedCode}
|
|
267
|
+
|
|
268
|
+
entity.componentFlags = newFlagValue;
|
|
269
|
+
${nullComponentCode};
|
|
270
|
+
},
|
|
122
271
|
`;
|
|
123
272
|
}
|
|
124
273
|
componentFlagValue <<= 1n;
|
|
@@ -173,13 +322,10 @@ function getEntityClassCode(componentMap, queries, debug) {
|
|
|
173
322
|
updateQueryMembershipsCode += `
|
|
174
323
|
if (
|
|
175
324
|
this.queryIndices[${queryIndex}] === -1
|
|
176
|
-
&& (
|
|
177
|
-
&& (
|
|
325
|
+
&& (thisComponentFlags & ${queryWithFlag}n) === ${queryWithFlag}n
|
|
326
|
+
&& (thisComponentFlags & ~${queryWithoutFlag}n) === thisComponentFlags
|
|
178
327
|
) {
|
|
179
|
-
|
|
180
|
-
console.log('adding entity to query "${queryName}"', this);
|
|
181
|
-
`}
|
|
182
|
-
this.componentFlags |= ${queryWithFlag}n;
|
|
328
|
+
// thisComponentFlags |= ${queryWithFlag}n;
|
|
183
329
|
if (queryEntityFreeIndices${queryIndex}.length > 0) {
|
|
184
330
|
const index = queryEntityFreeIndices${queryIndex}.pop();
|
|
185
331
|
this.queryIndices[${queryIndex}] = index;
|
|
@@ -191,28 +337,19 @@ function getEntityClassCode(componentMap, queries, debug) {
|
|
|
191
337
|
|
|
192
338
|
afterEntityAdded${queryIndex}(this);
|
|
193
339
|
|
|
194
|
-
${!
|
|
340
|
+
${!options?.logQueries ? "" : `
|
|
195
341
|
console.log('added entity to query "${queryName}"', this);
|
|
196
342
|
`}
|
|
197
343
|
} else if (
|
|
198
344
|
this.queryIndices[${queryIndex}] !== -1
|
|
199
345
|
&& (
|
|
200
|
-
(
|
|
201
|
-
|| (
|
|
346
|
+
(thisComponentFlags & ${queryWithFlag}n) !== ${queryWithFlag}n
|
|
347
|
+
|| (thisComponentFlags & ~${queryWithoutFlag}n) !== thisComponentFlags
|
|
202
348
|
)
|
|
203
349
|
) {
|
|
204
350
|
|
|
205
351
|
if (queryEntityRemovedListeners${queryIndex}.length > 0) {
|
|
206
|
-
|
|
207
|
-
// before calling the listeners, toggle the components that were removed, if any
|
|
208
|
-
// this is so that in the listener, the entity's hasComponent() calls correctly
|
|
209
|
-
// show that the component is still there, since the removal hasn't happened yet
|
|
210
|
-
this.componentFlags &= ~componentFlagThatWasRemoved;
|
|
211
|
-
beforeEntityRemoved${queryIndex}(this);
|
|
212
|
-
this.componentFlags &= ~componentFlagThatWasRemoved;
|
|
213
|
-
} else {
|
|
214
|
-
beforeEntityRemoved${queryIndex}(this);
|
|
215
|
-
}
|
|
352
|
+
beforeEntityRemoved${queryIndex}(this);
|
|
216
353
|
}
|
|
217
354
|
|
|
218
355
|
const index = this.queryIndices[${queryIndex}];
|
|
@@ -220,7 +357,7 @@ function getEntityClassCode(componentMap, queries, debug) {
|
|
|
220
357
|
queryEntities${queryIndex}[index] = null;
|
|
221
358
|
queryEntityFreeIndices${queryIndex}.push(index);
|
|
222
359
|
|
|
223
|
-
${!
|
|
360
|
+
${!options?.logQueries ? "" : `
|
|
224
361
|
console.log('removed entity from query "${queryName}"', this);
|
|
225
362
|
`}
|
|
226
363
|
}
|
|
@@ -298,9 +435,10 @@ function getEntityClassCode(componentMap, queries, debug) {
|
|
|
298
435
|
${poolCreationCode}
|
|
299
436
|
${poolMapCreationCode}
|
|
300
437
|
${componentToEntityCode}
|
|
301
|
-
|
|
438
|
+
${referenceComponentFunctionsCode}
|
|
439
|
+
const componentIndices = new Map([
|
|
302
440
|
${componentIndicesCode}
|
|
303
|
-
|
|
441
|
+
]);
|
|
304
442
|
const addComponentFunctions = [
|
|
305
443
|
${addComponentFunctionsCode}
|
|
306
444
|
];
|
|
@@ -445,41 +583,36 @@ function getEntityClassCode(componentMap, queries, debug) {
|
|
|
445
583
|
}
|
|
446
584
|
|
|
447
585
|
addComponent(key, data, updateQueryMemberships = true) {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
586
|
+
${options?.skipSafetyChecks ? "" : `
|
|
587
|
+
if ((this.componentFlags & componentFlags[componentIndices.get(key)]) !== 0n) {
|
|
588
|
+
console.error('this entity:', this);
|
|
589
|
+
throw new Error('DEV-only check: Tried to add component "'+key+'" that this entity already has');
|
|
590
|
+
}
|
|
591
|
+
`}
|
|
451
592
|
|
|
452
|
-
|
|
593
|
+
(addComponentFunctions[componentIndices.get(key)])(data, this, key, updateQueryMemberships);
|
|
453
594
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
}
|
|
595
|
+
${!options?.logComponents ? "" : `
|
|
596
|
+
console.log('added component "'+key+'" to entity, updateQueryMemberships = ', updateQueryMemberships, 'entity = ', this);
|
|
597
|
+
`}
|
|
457
598
|
}
|
|
458
599
|
|
|
459
600
|
removeComponent(key, updateQueryMemberships = true) {
|
|
460
|
-
const componentIndex = componentIndices
|
|
461
|
-
|
|
462
|
-
${!debug ? "" : `
|
|
601
|
+
const componentIndex = componentIndices.get(key);
|
|
602
|
+
${options?.skipSafetyChecks ? "" : `
|
|
463
603
|
if ((this.componentFlags & componentFlags[componentIndex]) === 0n) {
|
|
464
604
|
console.error('this entity:', this);
|
|
465
605
|
throw new Error('DEV-only check: Tried to remove component "'+key+'" that this entity does not have');
|
|
466
606
|
}
|
|
467
607
|
`}
|
|
468
|
-
this
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
const fn = removeComponentFunctions[componentIndex];
|
|
475
|
-
if (fn) {
|
|
476
|
-
fn(this[key], this, key);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
this[key] = null;
|
|
608
|
+
(removeComponentFunctions[componentIndex])(this[key], this, key, updateQueryMemberships);
|
|
609
|
+
${!options?.logComponents ? "" : `
|
|
610
|
+
console.log('removed component "'+key+'" from entity', this);
|
|
611
|
+
`}
|
|
480
612
|
}
|
|
481
613
|
|
|
482
614
|
destroy(updateQueryMemberships = true) {
|
|
615
|
+
const thisComponentFlags = this.componentFlags;
|
|
483
616
|
${destroyCode}
|
|
484
617
|
if (this.version & 1 === 1) {
|
|
485
618
|
${""}
|
|
@@ -493,10 +626,10 @@ function getEntityClassCode(componentMap, queries, debug) {
|
|
|
493
626
|
}
|
|
494
627
|
|
|
495
628
|
hasComponent(key) {
|
|
496
|
-
return (this.componentFlags & componentFlags[componentIndices
|
|
629
|
+
return (this.componentFlags & componentFlags[componentIndices.get(key)]) !== 0n;
|
|
497
630
|
}
|
|
498
631
|
|
|
499
|
-
updateQueryMemberships(
|
|
632
|
+
updateQueryMemberships(thisComponentFlags = this.componentFlags) {
|
|
500
633
|
${updateQueryMembershipsCode}
|
|
501
634
|
}
|
|
502
635
|
|
|
@@ -813,9 +946,10 @@ function getEntityClassCode(componentMap, queries, debug) {
|
|
|
813
946
|
|
|
814
947
|
theClass.queries = queries;
|
|
815
948
|
theClass.componentToEntity = componentToEntity;
|
|
949
|
+
theClass.componentIndices = componentIndices;
|
|
816
950
|
theClass.components = componentPools;
|
|
817
951
|
theClass.componentsWithEntities = {
|
|
818
|
-
${
|
|
952
|
+
${componentWithEntityIteratorFunctionsCode}
|
|
819
953
|
};
|
|
820
954
|
|
|
821
955
|
theClass.create = function(data, updateQueryMemberships = true) {
|
|
@@ -835,17 +969,30 @@ function getEntityClassCode(componentMap, queries, debug) {
|
|
|
835
969
|
}
|
|
836
970
|
}
|
|
837
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
|
+
|
|
838
984
|
return theClass;
|
|
839
985
|
`;
|
|
840
|
-
if (
|
|
986
|
+
if (options?.logCode) {
|
|
841
987
|
console.log("code", code);
|
|
842
988
|
}
|
|
843
989
|
return code;
|
|
844
990
|
}
|
|
845
|
-
function createEntityClass(
|
|
846
|
-
return (componentMap, queries) => new Function("componentMap", "queryMap", getEntityClassCode(componentMap, queries,
|
|
991
|
+
function createEntityClass(options) {
|
|
992
|
+
return (componentMap, queries) => new Function("componentMap", "queryMap", getEntityClassCode(componentMap, queries, options))(componentMap, queries);
|
|
847
993
|
}
|
|
848
994
|
export {
|
|
995
|
+
LazyComponent,
|
|
849
996
|
createEntityClass,
|
|
850
997
|
getEntityClassCode
|
|
851
998
|
};
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@perplexdotgg/mecs",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MECS - Monomorph ECS - A high-performance Entity Component System for TypeScript and JavaScript, designed for games and simulations.",
|
|
3
|
+
"version": "0.3.0",
|
|
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",
|
|
@@ -26,11 +27,17 @@
|
|
|
26
27
|
"test": "vitest",
|
|
27
28
|
"bench": "vitest bench"
|
|
28
29
|
},
|
|
29
|
-
"
|
|
30
|
-
"monomorph": "^1.
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"monomorph": "^1.5.5"
|
|
32
|
+
},
|
|
33
|
+
"peerDependenciesMeta": {
|
|
34
|
+
"monomorph": {
|
|
35
|
+
"optional": true
|
|
36
|
+
}
|
|
31
37
|
},
|
|
32
38
|
"devDependencies": {
|
|
33
39
|
"@types/node": "^25.0.1",
|
|
40
|
+
"monomorph": "^1.5.5",
|
|
34
41
|
"ts-node": "^10.9.2",
|
|
35
42
|
"tslib": "^2.8.1",
|
|
36
43
|
"typescript": "^5.9.3",
|