@codehz/ecs 0.10.0 → 0.10.2
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/dist/builder.d.mts +122 -79
- package/dist/world.mjs +20 -6
- package/dist/world.mjs.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/component/singleton.test.ts +50 -5
- package/src/world/operations.ts +15 -3
- package/src/world/singleton.ts +3 -2
- package/src/world/world.ts +129 -78
package/dist/builder.d.mts
CHANGED
|
@@ -1022,8 +1022,9 @@ interface SingletonHandleOps<T> {
|
|
|
1022
1022
|
/**
|
|
1023
1023
|
* Explicit handle for a singleton component (component-as-entity).
|
|
1024
1024
|
*
|
|
1025
|
-
* This
|
|
1026
|
-
*
|
|
1025
|
+
* This is the preferred API for singleton components.
|
|
1026
|
+
* `world.set(componentId, value)` remains available only as a deprecated
|
|
1027
|
+
* compatibility shorthand.
|
|
1027
1028
|
*
|
|
1028
1029
|
* @example
|
|
1029
1030
|
* const config = world.singleton(Config);
|
|
@@ -1050,6 +1051,7 @@ declare class SingletonHandle<T = void> {
|
|
|
1050
1051
|
* Manages entities and components
|
|
1051
1052
|
*/
|
|
1052
1053
|
declare class World {
|
|
1054
|
+
private static readonly DEPRECATED_SINGLETON_SET_SHORTHAND_WARNING;
|
|
1053
1055
|
private entityIdManager;
|
|
1054
1056
|
private entityReferences;
|
|
1055
1057
|
/** Sparse relation storage (for components created with `sparse: true`), shared with all Archetype instances */
|
|
@@ -1112,37 +1114,43 @@ declare class World {
|
|
|
1112
1114
|
*/
|
|
1113
1115
|
exists(entityId: EntityId): boolean;
|
|
1114
1116
|
/**
|
|
1115
|
-
*
|
|
1117
|
+
* Marks a void component as present on an entity.
|
|
1116
1118
|
* The change is buffered and takes effect after calling `world.sync()`.
|
|
1117
|
-
* If the entity does not exist, throws an error.
|
|
1118
1119
|
*
|
|
1119
|
-
* @
|
|
1120
|
-
*
|
|
1120
|
+
* @throws {Error} If the entity does not exist
|
|
1121
|
+
* @throws {Error} If the component type is invalid or is a wildcard relation
|
|
1121
1122
|
*
|
|
1122
|
-
* @
|
|
1123
|
-
*
|
|
1123
|
+
* @example
|
|
1124
|
+
* world.set(entity, Marker);
|
|
1125
|
+
* world.sync();
|
|
1126
|
+
*/
|
|
1127
|
+
set(entityId: EntityId, componentType: EntityId<void>): void;
|
|
1128
|
+
/**
|
|
1129
|
+
* @deprecated Use `world.singleton(componentId).set(value)` or `world.set(componentId, componentId, value)` instead.
|
|
1130
|
+
* Compatibility shorthand for singleton component data when the second argument is not a number.
|
|
1131
|
+
*
|
|
1132
|
+
* @throws {Error} If the component entity does not exist
|
|
1133
|
+
*
|
|
1134
|
+
* @example
|
|
1135
|
+
* world.set(GlobalConfig, { debug: true });
|
|
1136
|
+
* world.sync();
|
|
1137
|
+
*/
|
|
1138
|
+
set<T>(componentId: ComponentId<T>, component: Exclude<NoInfer<T>, number>): void;
|
|
1139
|
+
/**
|
|
1140
|
+
* Adds or updates component data on an entity.
|
|
1141
|
+
* The change is buffered and takes effect after calling `world.sync()`.
|
|
1124
1142
|
*
|
|
1125
1143
|
* @throws {Error} If the entity does not exist
|
|
1126
1144
|
* @throws {Error} If the component type is invalid or is a wildcard relation
|
|
1127
1145
|
*
|
|
1128
1146
|
* @example
|
|
1129
1147
|
* world.set(entity, Position, { x: 10, y: 20 });
|
|
1130
|
-
* world.
|
|
1131
|
-
* world.singleton(GlobalConfig).set({ debug: true }); // singleton component
|
|
1132
|
-
* world.sync(); // Apply changes
|
|
1148
|
+
* world.sync();
|
|
1133
1149
|
*/
|
|
1134
|
-
set(entityId: EntityId, componentType: EntityId<void>): void;
|
|
1135
1150
|
set<T>(entityId: EntityId, componentType: EntityId<T>, component: NoInfer<T>): void;
|
|
1136
1151
|
/**
|
|
1137
1152
|
* Removes a component from an entity.
|
|
1138
1153
|
* The change is buffered and takes effect after calling `world.sync()`.
|
|
1139
|
-
* If the entity does not exist, throws an error.
|
|
1140
|
-
*
|
|
1141
|
-
* @overload remove<T>(entityId: EntityId, componentType: EntityId<T>): void
|
|
1142
|
-
* Removes a component from an entity.
|
|
1143
|
-
*
|
|
1144
|
-
* @overload remove<T>(componentId: ComponentId<T>): void
|
|
1145
|
-
* Removes a singleton component (shorthand for remove(componentId, componentId)).
|
|
1146
1154
|
*
|
|
1147
1155
|
* @template T - The component data type
|
|
1148
1156
|
* @param entityId - The entity identifier
|
|
@@ -1153,11 +1161,22 @@ declare class World {
|
|
|
1153
1161
|
*
|
|
1154
1162
|
* @example
|
|
1155
1163
|
* world.remove(entity, Position);
|
|
1164
|
+
* world.sync(); // Apply changes
|
|
1165
|
+
*/
|
|
1166
|
+
remove<T>(entityId: EntityId, componentType: EntityId<T>): void;
|
|
1167
|
+
/**
|
|
1168
|
+
* Removes a singleton component (shorthand for remove(componentId, componentId)).
|
|
1169
|
+
* The change is buffered and takes effect after calling `world.sync()`.
|
|
1170
|
+
*
|
|
1171
|
+
* @template T - The component data type
|
|
1172
|
+
*
|
|
1173
|
+
* @throws {Error} If the component entity does not exist
|
|
1174
|
+
*
|
|
1175
|
+
* @example
|
|
1156
1176
|
* world.remove(GlobalConfig); // Remove singleton component
|
|
1157
1177
|
* world.sync(); // Apply changes
|
|
1158
1178
|
*/
|
|
1159
1179
|
remove<T>(componentId: ComponentId<T>): void;
|
|
1160
|
-
remove<T>(entityId: EntityId, componentType: EntityId<T>): void;
|
|
1161
1180
|
/**
|
|
1162
1181
|
* Deletes an entity and all its components from the world.
|
|
1163
1182
|
* The change is buffered and takes effect after calling `world.sync()`.
|
|
@@ -1191,15 +1210,8 @@ declare class World {
|
|
|
1191
1210
|
*
|
|
1192
1211
|
* Immediately reflects the current state without waiting for `sync()`.
|
|
1193
1212
|
*
|
|
1194
|
-
* @overload has<T>(entityId: EntityId, componentType: EntityId<T>): boolean
|
|
1195
|
-
* Checks if a specific component type is present on the entity.
|
|
1196
|
-
*
|
|
1197
|
-
* @overload has<T>(componentId: ComponentId<T>): boolean
|
|
1198
|
-
* Shorthand for checking a **singleton component** — a component that is its own
|
|
1199
|
-
* entity (component-as-entity pattern). Equivalent to `has(componentId, componentId)`.
|
|
1200
|
-
*
|
|
1201
1213
|
* @template T - The component data type
|
|
1202
|
-
* @param entityId - The entity identifier
|
|
1214
|
+
* @param entityId - The entity identifier
|
|
1203
1215
|
* @param componentType - The component type to check
|
|
1204
1216
|
* @returns `true` if the entity has the component, `false` otherwise
|
|
1205
1217
|
*
|
|
@@ -1208,55 +1220,73 @@ declare class World {
|
|
|
1208
1220
|
* if (world.has(entity, Position)) {
|
|
1209
1221
|
* const pos = world.get(entity, Position);
|
|
1210
1222
|
* }
|
|
1223
|
+
*/
|
|
1224
|
+
has<T>(entityId: EntityId, componentType: EntityId<T>): boolean;
|
|
1225
|
+
/**
|
|
1226
|
+
* Checks if a **singleton component** (component-as-entity) is present.
|
|
1227
|
+
* Equivalent to `has(componentId, componentId)`.
|
|
1211
1228
|
*
|
|
1212
|
-
*
|
|
1229
|
+
* Immediately reflects the current state without waiting for `sync()`.
|
|
1230
|
+
*
|
|
1231
|
+
* @template T - The component data type
|
|
1232
|
+
* @param componentId - The singleton component ID
|
|
1233
|
+
* @returns `true` if the singleton component exists, `false` otherwise
|
|
1234
|
+
*
|
|
1235
|
+
* @example
|
|
1213
1236
|
* if (world.has(GlobalConfig)) {
|
|
1214
1237
|
* const config = world.get(GlobalConfig);
|
|
1215
1238
|
* }
|
|
1216
|
-
*
|
|
1217
|
-
* // Use exists() for entity liveness checks
|
|
1218
|
-
* if (world.exists(entity)) { ... }
|
|
1219
1239
|
*/
|
|
1220
1240
|
has<T>(componentId: ComponentId<T>): boolean;
|
|
1221
|
-
has<T>(entityId: EntityId, componentType: EntityId<T>): boolean;
|
|
1222
1241
|
/**
|
|
1223
1242
|
* Retrieves a component from an entity.
|
|
1224
|
-
* For wildcard relations, returns all relations of that type.
|
|
1225
1243
|
* Throws an error if the component does not exist; use `has()` to check first or use `getOptional()`.
|
|
1226
1244
|
*
|
|
1227
|
-
* @
|
|
1228
|
-
*
|
|
1245
|
+
* @template T - The component data type
|
|
1246
|
+
* @param entityId - The entity identifier
|
|
1247
|
+
* @param componentType - The component type to retrieve
|
|
1248
|
+
*
|
|
1249
|
+
* @throws {Error} If the entity does not exist
|
|
1250
|
+
* @throws {Error} If the component does not exist on the entity
|
|
1229
1251
|
*
|
|
1230
|
-
* @
|
|
1231
|
-
*
|
|
1252
|
+
* @example
|
|
1253
|
+
* const position = world.get(entity, Position);
|
|
1254
|
+
*/
|
|
1255
|
+
get<T>(entityId: EntityId, componentType: EntityId<T>): T;
|
|
1256
|
+
/**
|
|
1257
|
+
* Retrieves all relations of a given wildcard type for an entity.
|
|
1258
|
+
* Returns an array of [target entity, component value] pairs.
|
|
1232
1259
|
*
|
|
1233
|
-
* @
|
|
1234
|
-
*
|
|
1260
|
+
* @template T - The component data type
|
|
1261
|
+
* @param entityId - The entity identifier
|
|
1262
|
+
* @param componentType - The wildcard relation type
|
|
1263
|
+
* @returns Array of [target entity, component value] pairs
|
|
1235
1264
|
*
|
|
1236
1265
|
* @throws {Error} If the entity does not exist
|
|
1237
|
-
* @throws {Error} If the component does not exist on the entity
|
|
1238
1266
|
*
|
|
1239
1267
|
* @example
|
|
1240
|
-
* const
|
|
1241
|
-
* const relations = world.get(entity, relation(Parent, "*")); // Wildcard relation
|
|
1268
|
+
* const relations = world.get(entity, relation(Parent, "*"));
|
|
1242
1269
|
*/
|
|
1243
|
-
get<T>(entityId: EntityId<T>): T;
|
|
1244
1270
|
get<T>(entityId: EntityId, componentType: WildcardRelationId<T>): [EntityId<unknown>, T][];
|
|
1245
|
-
get<T>(entityId: EntityId, componentType: EntityId<T>): T;
|
|
1246
1271
|
/**
|
|
1247
|
-
*
|
|
1248
|
-
* Returns `undefined` if the component does not exist.
|
|
1249
|
-
* For wildcard relations, returns `undefined` if there are no relations.
|
|
1272
|
+
* Retrieves the entity's primary component when called with only an entity ID.
|
|
1250
1273
|
*
|
|
1251
1274
|
* @template T - The component data type
|
|
1252
|
-
* @
|
|
1253
|
-
*
|
|
1275
|
+
* @param entityId - The entity identifier
|
|
1276
|
+
* @returns The component value
|
|
1254
1277
|
*
|
|
1255
|
-
* @
|
|
1256
|
-
*
|
|
1278
|
+
* @throws {Error} If the entity does not exist
|
|
1279
|
+
* @throws {Error} If the component does not exist on the entity
|
|
1280
|
+
*/
|
|
1281
|
+
get<T>(entityId: EntityId<T>): T;
|
|
1282
|
+
/**
|
|
1283
|
+
* Safely retrieves a component from an entity without throwing an error.
|
|
1284
|
+
* Returns `undefined` if the component does not exist.
|
|
1257
1285
|
*
|
|
1258
|
-
* @
|
|
1259
|
-
*
|
|
1286
|
+
* @template T - The component data type
|
|
1287
|
+
* @param entityId - The entity identifier
|
|
1288
|
+
* @param componentType - The component type to retrieve
|
|
1289
|
+
* @returns The component value wrapped in `{ value }`, or `undefined` if absent
|
|
1260
1290
|
*
|
|
1261
1291
|
* @throws {Error} If the entity does not exist
|
|
1262
1292
|
*
|
|
@@ -1266,13 +1296,34 @@ declare class World {
|
|
|
1266
1296
|
* console.log(position.value.x);
|
|
1267
1297
|
* }
|
|
1268
1298
|
*/
|
|
1269
|
-
getOptional<T>(entityId: EntityId<T>): {
|
|
1299
|
+
getOptional<T>(entityId: EntityId, componentType: EntityId<T>): {
|
|
1270
1300
|
value: T;
|
|
1271
1301
|
} | undefined;
|
|
1302
|
+
/**
|
|
1303
|
+
* Safely retrieves all matching relation values for a wildcard relation type.
|
|
1304
|
+
* Returns `undefined` if there are no relations.
|
|
1305
|
+
*
|
|
1306
|
+
* @template T - The component data type
|
|
1307
|
+
* @param entityId - The entity identifier
|
|
1308
|
+
* @param componentType - The wildcard relation type
|
|
1309
|
+
* @returns Array of [target, value] pairs wrapped in `{ value }`, or `undefined` if none
|
|
1310
|
+
*
|
|
1311
|
+
* @throws {Error} If the entity does not exist
|
|
1312
|
+
*/
|
|
1272
1313
|
getOptional<T>(entityId: EntityId, componentType: WildcardRelationId<T>): {
|
|
1273
1314
|
value: [EntityId<unknown>, T][];
|
|
1274
1315
|
} | undefined;
|
|
1275
|
-
|
|
1316
|
+
/**
|
|
1317
|
+
* Safely retrieves the entity's primary component without throwing an error.
|
|
1318
|
+
* Returns `undefined` if the component does not exist.
|
|
1319
|
+
*
|
|
1320
|
+
* @template T - The component data type
|
|
1321
|
+
* @param entityId - The entity identifier
|
|
1322
|
+
* @returns The component value wrapped in `{ value }`, or `undefined` if absent
|
|
1323
|
+
*
|
|
1324
|
+
* @throws {Error} If the entity does not exist
|
|
1325
|
+
*/
|
|
1326
|
+
getOptional<T>(entityId: EntityId<T>): {
|
|
1276
1327
|
value: T;
|
|
1277
1328
|
} | undefined;
|
|
1278
1329
|
/**
|
|
@@ -1377,21 +1428,15 @@ declare class World {
|
|
|
1377
1428
|
}): void;
|
|
1378
1429
|
/**
|
|
1379
1430
|
* Registers a lifecycle hook that responds to component changes.
|
|
1380
|
-
* The hook callback is invoked when components matching the specified types
|
|
1381
|
-
*
|
|
1382
|
-
* componentTypes: T,
|
|
1383
|
-
* hook: LifecycleHook<T> | LifecycleCallback<T>,
|
|
1384
|
-
* filter?: QueryFilter,
|
|
1385
|
-
* ): () => void
|
|
1386
|
-
* Registers a hook for multiple component types.
|
|
1387
|
-
* The hook is triggered when entities enter/exit the matching set.
|
|
1431
|
+
* The hook callback is invoked when components matching the specified types
|
|
1432
|
+
* are added, updated, or removed.
|
|
1388
1433
|
*
|
|
1389
1434
|
* @param componentTypes - Component types that define the matching entity set
|
|
1390
1435
|
* @param hook - Either a hook object with on_init/on_set/on_remove handlers, or a callback function
|
|
1391
1436
|
* @param filter - Optional query-style filter applied to the hook match set
|
|
1392
1437
|
* @returns A function that unsubscribes the hook when called
|
|
1393
1438
|
*
|
|
1394
|
-
* @throws {Error} If no required components are specified
|
|
1439
|
+
* @throws {Error} If no required components are specified
|
|
1395
1440
|
*
|
|
1396
1441
|
* @example
|
|
1397
1442
|
* const unsubscribe = world.hook([Position, Velocity], {
|
|
@@ -1526,32 +1571,30 @@ declare class World {
|
|
|
1526
1571
|
getMatchingArchetypes(componentTypes: EntityId<any>[]): Archetype[];
|
|
1527
1572
|
/**
|
|
1528
1573
|
* Queries entities with specific components.
|
|
1529
|
-
* For simpler use cases, prefer using `createQuery()` with `forEach()` which is cached and more efficient.
|
|
1530
|
-
*
|
|
1531
|
-
* @overload query(componentTypes: EntityId<any>[]): EntityId[]
|
|
1532
1574
|
* Returns an array of entity IDs that have all specified components.
|
|
1533
|
-
*
|
|
1534
|
-
* @overload query<const T extends readonly EntityId<any>[]>(
|
|
1535
|
-
* componentTypes: T,
|
|
1536
|
-
* includeComponents: true,
|
|
1537
|
-
* ): Array<{ entity: EntityId; components: ComponentTuple<T> }>
|
|
1538
|
-
* Returns entities along with their component data.
|
|
1575
|
+
* For simpler use cases, prefer using `createQuery()` with `forEach()` which is cached and more efficient.
|
|
1539
1576
|
*
|
|
1540
1577
|
* @param componentTypes - Array of component types to query
|
|
1541
|
-
* @
|
|
1542
|
-
* @returns Array of entity IDs or objects with entities and components
|
|
1578
|
+
* @returns Array of entity IDs matching the query
|
|
1543
1579
|
*
|
|
1544
1580
|
* @example
|
|
1545
|
-
* // Just entity IDs
|
|
1546
1581
|
* const entities = world.query([Position, Velocity]);
|
|
1582
|
+
*/
|
|
1583
|
+
query(componentTypes: EntityId<any>[]): EntityId[];
|
|
1584
|
+
/**
|
|
1585
|
+
* Queries entities with specific components and returns their component data.
|
|
1586
|
+
*
|
|
1587
|
+
* @template T - The tuple of component types
|
|
1588
|
+
* @param componentTypes - Array of component types to query
|
|
1589
|
+
* @param includeComponents - Must be `true` to include component data
|
|
1590
|
+
* @returns Array of objects with entity and component data
|
|
1547
1591
|
*
|
|
1548
|
-
*
|
|
1592
|
+
* @example
|
|
1549
1593
|
* const results = world.query([Position, Velocity], true);
|
|
1550
1594
|
* results.forEach(({ entity, components: [pos, vel] }) => {
|
|
1551
1595
|
* pos.x += vel.x;
|
|
1552
1596
|
* });
|
|
1553
1597
|
*/
|
|
1554
|
-
query(componentTypes: EntityId<any>[]): EntityId[];
|
|
1555
1598
|
query<const T extends readonly EntityId<any>[]>(componentTypes: T, includeComponents: true): Array<{
|
|
1556
1599
|
entity: EntityId;
|
|
1557
1600
|
components: ComponentTuple<T>;
|
package/dist/world.mjs
CHANGED
|
@@ -626,8 +626,9 @@ var EntityBuilder = class {
|
|
|
626
626
|
/**
|
|
627
627
|
* Explicit handle for a singleton component (component-as-entity).
|
|
628
628
|
*
|
|
629
|
-
* This
|
|
630
|
-
*
|
|
629
|
+
* This is the preferred API for singleton components.
|
|
630
|
+
* `world.set(componentId, value)` remains available only as a deprecated
|
|
631
|
+
* compatibility shorthand.
|
|
631
632
|
*
|
|
632
633
|
* @example
|
|
633
634
|
* const config = world.singleton(Config);
|
|
@@ -2746,15 +2747,25 @@ function assertSetComponentTypeValid(componentType) {
|
|
|
2746
2747
|
/**
|
|
2747
2748
|
* Resolve the (entity, componentType, value) for a set() call.
|
|
2748
2749
|
*/
|
|
2749
|
-
function resolveSetOperation(entityId, componentTypeOrComponent, maybeComponent, exists = () => true) {
|
|
2750
|
+
function resolveSetOperation(entityId, componentTypeOrComponent, maybeComponent, argCount = 3, exists = () => true) {
|
|
2750
2751
|
const targetEntityId = entityId;
|
|
2752
|
+
if (argCount === 2 && isComponentId(targetEntityId) && typeof componentTypeOrComponent !== "number") {
|
|
2753
|
+
assertEntityExists(targetEntityId, "Component entity", exists);
|
|
2754
|
+
return {
|
|
2755
|
+
entityId: targetEntityId,
|
|
2756
|
+
componentType: targetEntityId,
|
|
2757
|
+
component: componentTypeOrComponent,
|
|
2758
|
+
deprecatedSingletonShorthand: true
|
|
2759
|
+
};
|
|
2760
|
+
}
|
|
2751
2761
|
const componentType = componentTypeOrComponent;
|
|
2752
2762
|
assertEntityExists(targetEntityId, "Entity", exists);
|
|
2753
2763
|
assertSetComponentTypeValid(componentType);
|
|
2754
2764
|
return {
|
|
2755
2765
|
entityId: targetEntityId,
|
|
2756
2766
|
componentType,
|
|
2757
|
-
component: maybeComponent
|
|
2767
|
+
component: maybeComponent,
|
|
2768
|
+
deprecatedSingletonShorthand: false
|
|
2758
2769
|
};
|
|
2759
2770
|
}
|
|
2760
2771
|
/**
|
|
@@ -2944,7 +2955,8 @@ function deserializeWorld(ctx, snapshot) {
|
|
|
2944
2955
|
* World class for ECS architecture
|
|
2945
2956
|
* Manages entities and components
|
|
2946
2957
|
*/
|
|
2947
|
-
var World = class {
|
|
2958
|
+
var World = class World {
|
|
2959
|
+
static DEPRECATED_SINGLETON_SET_SHORTHAND_WARNING = "world.set(componentId, value) for singleton components is deprecated; use world.singleton(componentId).set(value) or world.set(componentId, componentId, value) instead.";
|
|
2948
2960
|
entityIdManager = new EntityIdManager();
|
|
2949
2961
|
entityReferences = /* @__PURE__ */ new Map();
|
|
2950
2962
|
/** Sparse relation storage (for components created with `sparse: true`), shared with all Archetype instances */
|
|
@@ -3095,8 +3107,10 @@ var World = class {
|
|
|
3095
3107
|
if (this.componentEntities.exists(entityId)) return true;
|
|
3096
3108
|
return this.entityToArchetype.has(entityId);
|
|
3097
3109
|
}
|
|
3110
|
+
/** Internal implementation for `set()` overloads. */
|
|
3098
3111
|
set(entityId, componentTypeOrComponent, maybeComponent) {
|
|
3099
|
-
const { entityId: targetEntityId, componentType, component } = resolveSetOperation(entityId, componentTypeOrComponent, maybeComponent, (id) => this.exists(id));
|
|
3112
|
+
const { entityId: targetEntityId, componentType, component, deprecatedSingletonShorthand } = resolveSetOperation(entityId, componentTypeOrComponent, maybeComponent, arguments.length, (id) => this.exists(id));
|
|
3113
|
+
if (deprecatedSingletonShorthand) console.warn(World.DEPRECATED_SINGLETON_SET_SHORTHAND_WARNING);
|
|
3100
3114
|
this.commandBuffer.set(targetEntityId, componentType, component);
|
|
3101
3115
|
}
|
|
3102
3116
|
remove(entityId, componentType) {
|