archetype-ecs 1.1.0 → 1.2.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.
@@ -0,0 +1,86 @@
1
+ // Focused benchmark: archetype+TypedArrays vs bitECS at 1M entities
2
+
3
+ import {
4
+ createWorld, addEntity, addComponent, query
5
+ } from 'bitecs';
6
+ import { createEntityManager, component } from '../src/index.js';
7
+
8
+ const COUNT = 1_000_000;
9
+ const FRAMES = 500;
10
+
11
+ // archetype-ecs: component() with schema + forEach() hot path
12
+ function benchArchetypeTyped() {
13
+ const Position = component('BenchPos', { x: 'f32', y: 'f32' });
14
+ const Velocity = component('BenchVel', { vx: 'f32', vy: 'f32' });
15
+ const em = createEntityManager();
16
+
17
+ for (let i = 0; i < COUNT; i++) {
18
+ const id = em.createEntity();
19
+ em.addComponent(id, Position, { x: Math.random() * 100, y: Math.random() * 100 });
20
+ em.addComponent(id, Velocity, { vx: Math.random() * 10, vy: Math.random() * 10 });
21
+ }
22
+
23
+ const t0 = performance.now();
24
+ for (let f = 0; f < FRAMES; f++) {
25
+ em.forEach([Position, Velocity], (arch) => {
26
+ const px = arch.field(Position.x);
27
+ const py = arch.field(Position.y);
28
+ const vx = arch.field(Velocity.vx);
29
+ const vy = arch.field(Velocity.vy);
30
+ for (let i = 0; i < arch.count; i++) {
31
+ px[i] += vx[i];
32
+ py[i] += vy[i];
33
+ }
34
+ });
35
+ }
36
+ return (performance.now() - t0) / FRAMES;
37
+ }
38
+
39
+ // bitECS: sparse TypedArrays, query returns entity ID list
40
+ function benchBitECS() {
41
+ const world = createWorld();
42
+ const Position = {
43
+ x: new Float32Array(COUNT + 10),
44
+ y: new Float32Array(COUNT + 10)
45
+ };
46
+ const Velocity = {
47
+ vx: new Float32Array(COUNT + 10),
48
+ vy: new Float32Array(COUNT + 10)
49
+ };
50
+
51
+ for (let i = 0; i < COUNT; i++) {
52
+ const eid = addEntity(world);
53
+ addComponent(world, eid, Position);
54
+ addComponent(world, eid, Velocity);
55
+ Position.x[eid] = Math.random() * 100;
56
+ Position.y[eid] = Math.random() * 100;
57
+ Velocity.vx[eid] = Math.random() * 10;
58
+ Velocity.vy[eid] = Math.random() * 10;
59
+ }
60
+
61
+ const t0 = performance.now();
62
+ for (let f = 0; f < FRAMES; f++) {
63
+ const entities = query(world, [Position, Velocity]);
64
+ for (let i = 0; i < entities.length; i++) {
65
+ const eid = entities[i];
66
+ Position.x[eid] += Velocity.vx[eid];
67
+ Position.y[eid] += Velocity.vy[eid];
68
+ }
69
+ }
70
+ return (performance.now() - t0) / FRAMES;
71
+ }
72
+
73
+ // Warmup
74
+ benchArchetypeTyped();
75
+
76
+ console.log(`\n=== 1M entities, ${FRAMES} frames: Position += Velocity ===\n`);
77
+
78
+ const archTyped = benchArchetypeTyped();
79
+ console.log(` arch+typed: ${archTyped.toFixed(3)} ms/frame`);
80
+
81
+ const bit = benchBitECS();
82
+ console.log(` bitECS: ${bit.toFixed(3)} ms/frame`);
83
+
84
+ const ratio = bit / archTyped;
85
+ console.log(`\n → arch+typed is ${ratio.toFixed(1)}x sneller`);
86
+ console.log();
@@ -0,0 +1,166 @@
1
+ // Benchmark: typed (SoA TypedArrays) vs untyped (object arrays) components
2
+
3
+ import { createEntityManager, component } from '../src/index.js';
4
+
5
+ const COUNT = 1_000_000;
6
+ const FRAMES = 200;
7
+
8
+ // --- Typed: creation ---
9
+ function benchTypedCreate() {
10
+ const Pos = component('TP', 'f32', ['x', 'y']);
11
+ const Vel = component('TV', 'f32', ['vx', 'vy']);
12
+ const em = createEntityManager();
13
+
14
+ const t0 = performance.now();
15
+ for (let i = 0; i < COUNT; i++) {
16
+ em.createEntityWith(Pos, { x: i, y: i }, Vel, { vx: 1, vy: 1 });
17
+ }
18
+ return { em, Pos, Vel, time: performance.now() - t0 };
19
+ }
20
+
21
+ // --- Untyped: creation ---
22
+ function benchUntypedCreate() {
23
+ const Pos = component('UP');
24
+ const Vel = component('UV');
25
+ const em = createEntityManager();
26
+
27
+ const t0 = performance.now();
28
+ for (let i = 0; i < COUNT; i++) {
29
+ em.createEntityWith(Pos, { x: i, y: i }, Vel, { vx: 1, vy: 1 });
30
+ }
31
+ return { em, Pos, Vel, time: performance.now() - t0 };
32
+ }
33
+
34
+ // --- Typed: iteration with forEach + field() ---
35
+ function benchTypedIterate(em, Pos, Vel) {
36
+ const t0 = performance.now();
37
+ for (let f = 0; f < FRAMES; f++) {
38
+ em.forEach([Pos, Vel], (arch) => {
39
+ const px = arch.field(Pos.x);
40
+ const py = arch.field(Pos.y);
41
+ const vx = arch.field(Vel.vx);
42
+ const vy = arch.field(Vel.vy);
43
+ for (let i = 0; i < arch.count; i++) {
44
+ px[i] += vx[i];
45
+ py[i] += vy[i];
46
+ }
47
+ });
48
+ }
49
+ return (performance.now() - t0) / FRAMES;
50
+ }
51
+
52
+ // --- Untyped: iteration with forEach (object access) ---
53
+ function benchUntypedIterate(em, Pos, Vel) {
54
+ const t0 = performance.now();
55
+ for (let f = 0; f < FRAMES; f++) {
56
+ const ids = em.query([Pos, Vel]);
57
+ for (let i = 0; i < ids.length; i++) {
58
+ const pos = em.getComponent(ids[i], Pos);
59
+ const vel = em.getComponent(ids[i], Vel);
60
+ pos.x += vel.vx;
61
+ pos.y += vel.vy;
62
+ }
63
+ }
64
+ return (performance.now() - t0) / FRAMES;
65
+ }
66
+
67
+ // --- String SoA: creation + access ---
68
+ function benchStringSoA() {
69
+ const Name = component('SSoA', { name: 'string', tag: 'string' });
70
+ const em = createEntityManager();
71
+
72
+ const t0 = performance.now();
73
+ for (let i = 0; i < COUNT; i++) {
74
+ em.createEntityWith(Name, { name: `entity_${i}`, tag: 'npc' });
75
+ }
76
+ const createTime = performance.now() - t0;
77
+
78
+ // forEach field access
79
+ const t1 = performance.now();
80
+ let count = 0;
81
+ for (let f = 0; f < 50; f++) {
82
+ em.forEach([Name], (arch) => {
83
+ const names = arch.field(Name.name);
84
+ for (let i = 0; i < arch.count; i++) {
85
+ if (names[i].length > 5) count++;
86
+ }
87
+ });
88
+ }
89
+ const forEachTime = (performance.now() - t1) / 50;
90
+
91
+ // get() field access
92
+ const ids = em.query([Name]);
93
+ const t2 = performance.now();
94
+ let count2 = 0;
95
+ for (let f = 0; f < 50; f++) {
96
+ for (let i = 0; i < ids.length; i++) {
97
+ if (em.get(ids[i], Name.name).length > 5) count2++;
98
+ }
99
+ }
100
+ const getTime = (performance.now() - t2) / 50;
101
+
102
+ return { createTime, forEachTime, getTime, count, count2 };
103
+ }
104
+
105
+ // --- String untyped: creation + access ---
106
+ function benchStringUntyped() {
107
+ const Name = component('SUn');
108
+ const em = createEntityManager();
109
+
110
+ const t0 = performance.now();
111
+ for (let i = 0; i < COUNT; i++) {
112
+ em.createEntityWith(Name, { name: `entity_${i}`, tag: 'npc' });
113
+ }
114
+ const createTime = performance.now() - t0;
115
+
116
+ // getComponent access
117
+ const ids = em.query([Name]);
118
+ const t1 = performance.now();
119
+ let count = 0;
120
+ for (let f = 0; f < 50; f++) {
121
+ for (let i = 0; i < ids.length; i++) {
122
+ const n = em.getComponent(ids[i], Name);
123
+ if (n.name.length > 5) count++;
124
+ }
125
+ }
126
+ const accessTime = (performance.now() - t1) / 50;
127
+
128
+ return { createTime, accessTime, count };
129
+ }
130
+
131
+ // --- Run ---
132
+ console.log(`\n=== Typed vs Untyped: ${(COUNT / 1e6).toFixed(0)}M entities ===\n`);
133
+
134
+ // Warmup
135
+ benchTypedCreate();
136
+ benchUntypedCreate();
137
+
138
+ // Creation
139
+ const typed = benchTypedCreate();
140
+ const untyped = benchUntypedCreate();
141
+
142
+ console.log(`Creation (${(COUNT / 1e6).toFixed(0)}M entities, createEntityWith):`);
143
+ console.log(` typed: ${typed.time.toFixed(0)} ms`);
144
+ console.log(` untyped: ${untyped.time.toFixed(0)} ms`);
145
+ console.log(` ratio: ${(untyped.time / typed.time).toFixed(2)}x`);
146
+
147
+ // Iteration
148
+ const typedIter = benchTypedIterate(typed.em, typed.Pos, typed.Vel);
149
+ const untypedIter = benchUntypedIterate(untyped.em, untyped.Pos, untyped.Vel);
150
+
151
+ console.log(`\nIteration (${FRAMES} frames, Position += Velocity):`);
152
+ console.log(` typed (forEach+field): ${typedIter.toFixed(2)} ms/frame`);
153
+ console.log(` untyped (query+getComponent): ${untypedIter.toFixed(2)} ms/frame`);
154
+ console.log(` ratio: ${(untypedIter / typedIter).toFixed(1)}x slower`);
155
+
156
+ // String components
157
+ console.log(`\nString component (${(COUNT / 1e6).toFixed(0)}M entities, { name, tag }):`);
158
+ const strSoA = benchStringSoA();
159
+ const strUn = benchStringUntyped();
160
+ console.log(` SoA create: ${strSoA.createTime.toFixed(0)} ms`);
161
+ console.log(` untyped create: ${strUn.createTime.toFixed(0)} ms`);
162
+ console.log(` SoA forEach(field): ${strSoA.forEachTime.toFixed(1)} ms/frame`);
163
+ console.log(` SoA get(): ${strSoA.getTime.toFixed(1)} ms/frame`);
164
+ console.log(` untyped getComponent(): ${strUn.accessTime.toFixed(1)} ms/frame`);
165
+ console.log(` forEach vs getComponent: ${(strUn.accessTime / strSoA.forEachTime).toFixed(1)}x faster`);
166
+ console.log();
@@ -0,0 +1,267 @@
1
+ // Benchmark: archetype-ecs (current) vs archetype+TypedArrays (hypothetical) vs bitECS
2
+ // Tests: system loop, entity creation, component add/remove churn
3
+
4
+ import { createEntityManager } from '../src/EntityManager.js';
5
+ import {
6
+ createWorld, addEntity,
7
+ addComponent, removeComponent,
8
+ query
9
+ } from 'bitecs';
10
+
11
+ const ENTITY_COUNTS = [100, 1_000, 10_000, 100_000];
12
+ const FRAMES = 500;
13
+ const MAX_ENTITIES = 110_000;
14
+
15
+ const pad = (s, n) => String(s).padStart(n);
16
+
17
+ // =========================================================================
18
+ // System loop: Position += Velocity
19
+ // =========================================================================
20
+
21
+ function benchArchetypeLoop(entityCount) {
22
+ const em = createEntityManager();
23
+ const Position = Symbol('Position');
24
+ const Velocity = Symbol('Velocity');
25
+
26
+ for (let i = 0; i < entityCount; i++) {
27
+ const id = em.createEntity();
28
+ em.addComponent(id, Position, { x: Math.random() * 100, y: Math.random() * 100 });
29
+ em.addComponent(id, Velocity, { vx: Math.random() * 10, vy: Math.random() * 10 });
30
+ }
31
+
32
+ const t0 = performance.now();
33
+ for (let f = 0; f < FRAMES; f++) {
34
+ const entities = em.query([Position, Velocity]);
35
+ for (let i = 0; i < entities.length; i++) {
36
+ const pos = em.getComponent(entities[i], Position);
37
+ const vel = em.getComponent(entities[i], Velocity);
38
+ pos.x += vel.vx;
39
+ pos.y += vel.vy;
40
+ }
41
+ }
42
+ return (performance.now() - t0) / FRAMES;
43
+ }
44
+
45
+ // Simulates archetype-ecs with TypedArray backing:
46
+ // Dense arrays per archetype, but Float32Arrays instead of object arrays
47
+ function benchArchetypeTypedLoop(entityCount) {
48
+ // One archetype with all entities (they all have Position+Velocity)
49
+ const count = entityCount;
50
+ const px = new Float32Array(count);
51
+ const py = new Float32Array(count);
52
+ const vx = new Float32Array(count);
53
+ const vy = new Float32Array(count);
54
+ for (let i = 0; i < count; i++) {
55
+ px[i] = Math.random() * 100;
56
+ py[i] = Math.random() * 100;
57
+ vx[i] = Math.random() * 10;
58
+ vy[i] = Math.random() * 10;
59
+ }
60
+
61
+ const t0 = performance.now();
62
+ for (let f = 0; f < FRAMES; f++) {
63
+ // Dense iteration — no entity ID lookup, no getComponent, no sparse gaps
64
+ for (let i = 0; i < count; i++) {
65
+ px[i] += vx[i];
66
+ py[i] += vy[i];
67
+ }
68
+ }
69
+ return (performance.now() - t0) / FRAMES;
70
+ }
71
+
72
+ function benchBitECSLoop(entityCount) {
73
+ const world = createWorld();
74
+ const Position = {
75
+ x: new Float32Array(MAX_ENTITIES),
76
+ y: new Float32Array(MAX_ENTITIES)
77
+ };
78
+ const Velocity = {
79
+ vx: new Float32Array(MAX_ENTITIES),
80
+ vy: new Float32Array(MAX_ENTITIES)
81
+ };
82
+
83
+ for (let i = 0; i < entityCount; i++) {
84
+ const eid = addEntity(world);
85
+ addComponent(world, eid, Position);
86
+ addComponent(world, eid, Velocity);
87
+ Position.x[eid] = Math.random() * 100;
88
+ Position.y[eid] = Math.random() * 100;
89
+ Velocity.vx[eid] = Math.random() * 10;
90
+ Velocity.vy[eid] = Math.random() * 10;
91
+ }
92
+
93
+ const t0 = performance.now();
94
+ for (let f = 0; f < FRAMES; f++) {
95
+ const entities = query(world, [Position, Velocity]);
96
+ for (let i = 0; i < entities.length; i++) {
97
+ const eid = entities[i];
98
+ Position.x[eid] += Velocity.vx[eid];
99
+ Position.y[eid] += Velocity.vy[eid];
100
+ }
101
+ }
102
+ return (performance.now() - t0) / FRAMES;
103
+ }
104
+
105
+ // =========================================================================
106
+ // Entity creation with 2 components
107
+ // =========================================================================
108
+
109
+ function benchArchetypeCreate(entityCount) {
110
+ const em = createEntityManager();
111
+ const Position = Symbol('Position');
112
+ const Velocity = Symbol('Velocity');
113
+
114
+ const t0 = performance.now();
115
+ for (let i = 0; i < entityCount; i++) {
116
+ const id = em.createEntity();
117
+ em.addComponent(id, Position, { x: i, y: i });
118
+ em.addComponent(id, Velocity, { vx: 1, vy: 1 });
119
+ }
120
+ return performance.now() - t0;
121
+ }
122
+
123
+ function benchBitECSCreate(entityCount) {
124
+ const world = createWorld();
125
+ const Position = {
126
+ x: new Float32Array(entityCount + 10),
127
+ y: new Float32Array(entityCount + 10)
128
+ };
129
+ const Velocity = {
130
+ vx: new Float32Array(entityCount + 10),
131
+ vy: new Float32Array(entityCount + 10)
132
+ };
133
+
134
+ const t0 = performance.now();
135
+ for (let i = 0; i < entityCount; i++) {
136
+ const eid = addEntity(world);
137
+ addComponent(world, eid, Position);
138
+ addComponent(world, eid, Velocity);
139
+ Position.x[eid] = i;
140
+ Position.y[eid] = i;
141
+ Velocity.vx[eid] = 1;
142
+ Velocity.vy[eid] = 1;
143
+ }
144
+ return performance.now() - t0;
145
+ }
146
+
147
+ // =========================================================================
148
+ // Component add/remove churn
149
+ // =========================================================================
150
+
151
+ function benchArchetypeChurn(entityCount) {
152
+ const em = createEntityManager();
153
+ const Position = Symbol('Position');
154
+ const Health = Symbol('Health');
155
+
156
+ const ids = [];
157
+ for (let i = 0; i < entityCount; i++) {
158
+ const id = em.createEntity();
159
+ em.addComponent(id, Position, { x: 0, y: 0 });
160
+ ids.push(id);
161
+ }
162
+
163
+ const ops = Math.min(entityCount, 10_000);
164
+ const t0 = performance.now();
165
+ for (let i = 0; i < ops; i++) {
166
+ em.addComponent(ids[i], Health, { hp: 100 });
167
+ }
168
+ for (let i = 0; i < ops; i++) {
169
+ em.removeComponent(ids[i], Health);
170
+ }
171
+ return { ms: performance.now() - t0, ops: ops * 2 };
172
+ }
173
+
174
+ function benchBitECSChurn(entityCount) {
175
+ const world = createWorld();
176
+ const Position = {
177
+ x: new Float32Array(entityCount + 10),
178
+ y: new Float32Array(entityCount + 10)
179
+ };
180
+ const Health = {
181
+ hp: new Float32Array(entityCount + 10)
182
+ };
183
+
184
+ const eids = [];
185
+ for (let i = 0; i < entityCount; i++) {
186
+ const eid = addEntity(world);
187
+ addComponent(world, eid, Position);
188
+ Position.x[eid] = 0;
189
+ Position.y[eid] = 0;
190
+ eids.push(eid);
191
+ }
192
+
193
+ const ops = Math.min(entityCount, 10_000);
194
+ const t0 = performance.now();
195
+ for (let i = 0; i < ops; i++) {
196
+ addComponent(world, eids[i], Health);
197
+ Health.hp[eids[i]] = 100;
198
+ }
199
+ for (let i = 0; i < ops; i++) {
200
+ removeComponent(world, eids[i], Health);
201
+ }
202
+ return { ms: performance.now() - t0, ops: ops * 2 };
203
+ }
204
+
205
+ // =========================================================================
206
+ // RUN
207
+ // =========================================================================
208
+
209
+ // Warmup
210
+ benchArchetypeLoop(100);
211
+ benchArchetypeTypedLoop(100);
212
+ benchBitECSLoop(100);
213
+
214
+ console.log(`\n=== System loop: Position += Velocity (${FRAMES} frames, per-frame time) ===\n`);
215
+ console.log('Entities | arch (now) | arch+typed | bitECS | arch+typed vs bitECS');
216
+ console.log('------------|---------------|--------------|--------------|---------------------');
217
+
218
+ for (const count of ENTITY_COUNTS) {
219
+ const arch = benchArchetypeLoop(count);
220
+ const archTyped = benchArchetypeTypedLoop(count);
221
+ const bit = benchBitECSLoop(count);
222
+ const vsNow = arch / archTyped;
223
+ const vsBit = bit / archTyped;
224
+ console.log(
225
+ `${pad(count.toLocaleString(), 11)} | ` +
226
+ `${pad(arch.toFixed(3), 10)} ms | ` +
227
+ `${pad(archTyped.toFixed(3), 9)} ms | ` +
228
+ `${pad(bit.toFixed(3), 9)} ms | ` +
229
+ `${vsBit > 1.1 ? `arch+typed ${vsBit.toFixed(1)}x sneller` : vsBit < 0.9 ? `bitECS ${(1/vsBit).toFixed(1)}x sneller` : '~gelijk'}`
230
+ );
231
+ }
232
+
233
+ console.log(`\n=== Entity creation (with 2 components) ===\n`);
234
+ console.log('Entities | archetype-ecs | bitECS | Δ');
235
+ console.log('------------|---------------|--------------|-------');
236
+
237
+ for (const count of ENTITY_COUNTS) {
238
+ const arch = benchArchetypeCreate(count);
239
+ const bit = benchBitECSCreate(count);
240
+ const ratio = arch / bit;
241
+ const winner = ratio > 1.1 ? `bitECS ${ratio.toFixed(1)}x` : ratio < 0.9 ? `arch ${(1/ratio).toFixed(1)}x` : '~gelijk';
242
+ console.log(
243
+ `${pad(count.toLocaleString(), 11)} | ` +
244
+ `${pad(arch.toFixed(1), 10)} ms | ` +
245
+ `${pad(bit.toFixed(1), 9)} ms | ` +
246
+ `${winner}`
247
+ );
248
+ }
249
+
250
+ console.log(`\n=== Component add/remove churn (10k ops) ===\n`);
251
+ console.log('Entities | archetype-ecs | bitECS | Δ');
252
+ console.log('------------|---------------|--------------|-------');
253
+
254
+ for (const count of [1_000, 10_000, 100_000]) {
255
+ const arch = benchArchetypeChurn(count);
256
+ const bit = benchBitECSChurn(count);
257
+ const ratio = arch.ms / bit.ms;
258
+ const winner = ratio > 1.1 ? `bitECS ${ratio.toFixed(1)}x` : ratio < 0.9 ? `arch ${(1/ratio).toFixed(1)}x` : '~gelijk';
259
+ console.log(
260
+ `${pad(count.toLocaleString(), 11)} | ` +
261
+ `${pad(arch.ms.toFixed(1), 10)} ms | ` +
262
+ `${pad(bit.ms.toFixed(1), 9)} ms | ` +
263
+ `${winner}`
264
+ );
265
+ }
266
+
267
+ console.log();
package/package.json CHANGED
@@ -1,16 +1,34 @@
1
1
  {
2
2
  "name": "archetype-ecs",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Lightweight archetype-based Entity Component System",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
7
  "module": "src/index.js",
8
+ "types": "src/index.d.ts",
8
9
  "exports": {
9
- ".": "./src/index.js"
10
+ ".": {
11
+ "types": "./src/index.d.ts",
12
+ "default": "./src/index.js"
13
+ }
10
14
  },
11
15
  "scripts": {
12
- "test": "node --test tests/"
16
+ "test": "node --test tests/ && npx tsc --noEmit",
17
+ "bench": "node --expose-gc bench/multi-ecs-bench.js"
13
18
  },
14
- "keywords": ["ecs", "entity", "component", "system", "archetype", "gamedev"],
15
- "license": "MIT"
19
+ "keywords": [
20
+ "ecs",
21
+ "entity",
22
+ "component",
23
+ "system",
24
+ "archetype",
25
+ "gamedev"
26
+ ],
27
+ "license": "MIT",
28
+ "devDependencies": {
29
+ "bitecs": "^0.4.0",
30
+ "harmony-ecs": "^0.0.12",
31
+ "miniplex": "^2.0.0",
32
+ "wolf-ecs": "^2.0.0"
33
+ }
16
34
  }
@@ -0,0 +1,21 @@
1
+ export const TYPED = Symbol('typed');
2
+
3
+ export const TYPE_MAP = {
4
+ 'f32': Float32Array,
5
+ 'f64': Float64Array,
6
+ 'i8': Int8Array,
7
+ 'i16': Int16Array,
8
+ 'i32': Int32Array,
9
+ 'u8': Uint8Array,
10
+ 'u16': Uint16Array,
11
+ 'u32': Uint32Array,
12
+ 'string': Array,
13
+ };
14
+
15
+ /** @type {Map<symbol, Record<string, typeof Float32Array>>} */
16
+ export const componentSchemas = new Map();
17
+
18
+ /** Extract the underlying symbol from a component object or pass through a plain symbol */
19
+ export function toSym(type) {
20
+ return type._sym || type;
21
+ }