diff --git a/README.md b/README.md index 10b1008..369c431 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,26 @@ ECS.getEntities(world, [ 'test_component' ]).length // because we are not defer ``` + +### removeEntities Example + +You can use `ECS.removeEntities` to remove all entities with a specified component. For example, if you have entities with an `ephemeral` component and want to delete them from the world, you can do this: + +```javascript +// remove all entities that have an 'ephemeral' component +ECS.removeEntities(world, ['ephemeral']); +``` + +The `removeEntities` function also supports the **not filter**, which allows you to specify components that should **not** be present on the entities you want to remove. For example, to remove entities that have a `transform` component but lack a `health` component, you can use the following code: + +```javascript +// remove all entities that have a 'transform' component and lack a 'health' component +ECS.removeEntities(world, ['transform', '!health']); +``` + +This functionality can be useful for cleaning up entities that meet specific component criteria in your world. + + ### get entity by unique id ECS will generate a unique integer id for every entity created in a world: diff --git a/ecs.js b/ecs.js index 4867d03..4094d0a 100644 --- a/ecs.js +++ b/ecs.js @@ -289,6 +289,42 @@ export function addComponentToEntity (world, entity, componentName, componentDat } } +/** + * Remove entities from the world that match the given component criteria. + * @param {World} world - The world containing the entities. + * @param {string[]} componentNames - Array of component names to filter by. + * Supports 'not' filters by prefixing component names with '!'. + */ +export function removeEntities(world, componentNames) { + const entitiesToRemove = world.entities.filter((entity) => { + for (const componentName of componentNames) { + const isNotFilter = componentName.startsWith('!'); + const actualComponentName = isNotFilter + ? componentName.slice(1) + : componentName; + + if (isNotFilter) { + // If it's a 'not' filter (prefixed with '!'), the entity should NOT have the component + if (entity[actualComponentName]) { + return false; + } + } else { + // If it's a regular filter, the entity MUST have the component + if (!entity[actualComponentName]) { + return false; + } + } + } + return true; + }); + + // Remove each matched entity + for (const entity of entitiesToRemove) { + removeEntity(world, entity, false); // remove immediately + } +} + + /** * Get entities from the world with all provided components. Optionally, @@ -687,6 +723,7 @@ export default { getEntities, getEntity, removeEntity, + removeEntities, addSystem, preFixedUpdate, fixedUpdate, diff --git a/test/removeEntity.js b/test/removeEntity.js index af6d735..5ccccc0 100644 --- a/test/removeEntity.js +++ b/test/removeEntity.js @@ -220,3 +220,42 @@ import tap from 'tap' ECS.cleanup(w) } + +{ + // Test removeEntities function with 'not' filter support + const w = ECS.addWorld(); + + const e1 = ECS.addEntity(w); + ECS.addComponent(w, e1, 'ephemeral', {}); + + const e2 = ECS.addEntity(w); + ECS.addComponent(w, e2, 'ephemeral', {}); + ECS.addComponent(w, e2, 'health', { hp: 100 }); + + const e3 = ECS.addEntity(w); + ECS.addComponent(w, e3, 'transform', {}); + ECS.addComponent(w, e3, 'health', { hp: 100 }); + + tap.equal(w.entities.length, 3, '3 entities should be present initially'); + + // Remove all entities with 'ephemeral' component + ECS.removeEntities(w, ['ephemeral']); + ECS.cleanup(w); // process deferred removals + tap.equal(w.entities.length, 1, 'only 1 entity should remain after removing all entities with "ephemeral"'); + + // Add entities again for next test + const e4 = ECS.addEntity(w); + ECS.addComponent(w, e4, 'transform', {}); + ECS.addComponent(w, e4, 'ephemeral', {}); + + const e5 = ECS.addEntity(w); + ECS.addComponent(w, e5, 'transform', {}); + ECS.addComponent(w, e5, 'health', { hp: 100 }); + + tap.equal(w.entities.length, 3, '3 entities should be present after adding new entities'); + + // Remove entities with 'transform' but not 'health' + ECS.removeEntities(w, ['transform', '!health']); + ECS.cleanup(w); // process deferred removals + tap.equal(w.entities.length, 2, '2 entities should remain after removing entities with "transform" but not "health"'); +} diff --git a/types/ecs.d.ts b/types/ecs.d.ts index 0611f2b..959b1bb 100644 --- a/types/ecs.d.ts +++ b/types/ecs.d.ts @@ -119,6 +119,13 @@ export function removeComponentFromEntity(world: World, entity: Entity, componen * @returns {void} returns early if entity does not exist in world */ export function removeEntity(world: World, entity: Entity, deferredRemoval?: boolean): void; +/** + * Remove entities from the world that match the given component criteria. + * @param {World} world - The world containing the entities. + * @param {string[]} componentNames - Array of component names to filter by. + * Supports 'not' filters by prefixing component names with '!'. + */ +export function removeEntities(world: World, componentNames: string[]): void; /** * Get entities from the world with all provided components. Optionally, * @param {World} world diff --git a/types/ecs.d.ts.map b/types/ecs.d.ts.map index 11f39bb..aceda53 100644 --- a/types/ecs.d.ts.map +++ b/types/ecs.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"ecs.d.ts","sourceRoot":"","sources":["../ecs.js"],"names":[],"mappings":"AAIA;;GAEG;AAEH;;GAEG;AAEH;;GAEG;AAEH;;;;GAIG;AAEH;;GAEG;AAEH;;GAEG;AAEH;;;;;;;;GAQG;AAEH;;;;;GAKG;AAEH;;GAEG;AAEH;;GAEG;AAEH;;;;GAIG;AAEH;;GAEG;AAEH;;;;GAIG;AAEH;;;;;;;;;;;;;;;GAeG;AAEH;;;;;;;;GAQG;AAEH;;;;;;GAMG;AACH,sCAHW,MAAM,GACJ,KAAK,CAmEjB;AAGD;;;;GAIG;AACH,oCAHW,KAAK,GACH,MAAM,CAclB;AAGD,0DAEC;AAGD,8DAEC;AAGD;;;;;;;GAOG;AACH,4CANW,KAAK,UACL,MAAM,iBACN,MAAM,kBACN,SAAS,GACP,IAAI,CA+BhB;AAGD;;;;;;;GAOG;AACF,iDANU,KAAK,UACL,MAAM,iBACN,MAAM,oBACN,OAAO,GACL,IAAI,CAchB;AAGD;;;;;;GAMG;AACF,oCALU,KAAK,UACL,MAAM,oBACN,OAAO,GACL,IAAI,CAkBhB;AAGD;;;;;;;;;;;;;GAaG;AACH,mCAZW,KAAK,kBACL,MAAM,EAAE,iBAKR,YAAY,qBAEV,cAAc,GAEd,MAAM,EAAE,CAwCpB;AAGD;;;;;;;;;GASG;AACH,iCARW,KAAK,kBACL,MAAM,EAAE,GAKN,MAAM,GAAC,IAAI,CAIvB;AA8BD;;;;GAIG;AACH,iCAHW,KAAK,MACL,cAAc,QA+BxB;AAED;;;;GAIG;AACH,sCAHW,KAAK,MACL,MAAM,QAUhB;AAGD;;;;GAIG;AACH,mCAHW,KAAK,MACL,MAAM,QAUhB;AAED;;;;GAIG;AACH,uCAHW,KAAK,MACL,MAAM,QAUhB;AAGD;;;;GAIG;AACH,iCAHW,KAAK,MACL,MAAM,QAUhB;AAED;;;;GAIG;AACH,8BAHW,KAAK,MACL,MAAM,QAUhB;AAED;;;;GAIG;AACH,kCAHW,KAAK,MACL,MAAM,QAUhB;AA2GD;;;GAGG;AACH,+BAFW,KAAK,QAkDf;;;;;;;;;;;;;;;;;;;;;;;;;2BA7pBa,OAAO,GAAG,SAAS;6BAInB,MAAM,EAAE;wBAIR,GAAG;qBAIJ;IACZ,CAAK,GAAG,EAAE,MAAM,GAAI,SAAS,CAAA;CAC1B;iCAIU,MAAM,EAAE;mCAIR,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI;;uBAKxB,oBAAoB;oBACpB,oBAAoB;wBACpB,oBAAoB;kBACpB,oBAAoB;eACpB,oBAAoB;mBACpB,oBAAoB;;6BAIjB;IACZ,CAAK,KAAK,EAAE,KAAK,GAAK,MAAM,CAAA;CACzB;;0BASS;IAAE,CAAE,GAAG,EAAE,MAAM,GAAI,QAAQ,CAAA;CAAE;;WAKhC,WAAW;aACX,WAAW;;wBAIR;IAAE,CAAE,QAAQ,EAAE,MAAM,GAAI,kBAAkB,CAAA;CAAE;;cAK/C,MAAM,EAAE;;;;gBACR,MAAM,EAAE;;;iBAKR,MAAM;;;;oBACN;QAAE,CAAE,GAAG,EAAE,MAAM,GAAI,MAAM,CAAA;KAAE;;;;2BAC3B;QAAE,CAAE,GAAG,EAAE,MAAM,GAAI,MAAM,CAAA;KAAE;aAC3B;QACL,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE;YACb,CAAQ,GAAG,EAAE,MAAM,GAAI,MAAM,CAAC;SACzB,CAAC;KACH,EAAE;;;;;mBACI,MAAM;;;;kBAEN,MAAM;;;cAKN,MAAM,EAAE;aACR,SAAS;aACT,MAAM,EAAE;eACR,iBAAiB;sBACjB,kBAAkB;WAClB,UAAU"} \ No newline at end of file +{"version":3,"file":"ecs.d.ts","sourceRoot":"","sources":["../ecs.js"],"names":[],"mappings":"AAIA;;GAEG;AAEH;;GAEG;AAEH;;GAEG;AAEH;;;;GAIG;AAEH;;GAEG;AAEH;;GAEG;AAEH;;;;;;;;GAQG;AAEH;;;;;GAKG;AAEH;;GAEG;AAEH;;GAEG;AAEH;;;;GAIG;AAEH;;GAEG;AAEH;;;;GAIG;AAEH;;;;;;;;;;;;;;;GAeG;AAEH;;;;;;;;GAQG;AAEH;;;;;;GAMG;AACH,sCAHW,MAAM,GACJ,KAAK,CAmEjB;AAGD;;;;GAIG;AACH,oCAHW,KAAK,GACH,MAAM,CAclB;AAGD,0DAEC;AAGD,8DAEC;AAGD;;;;;;;GAOG;AACH,4CANW,KAAK,UACL,MAAM,iBACN,MAAM,kBACN,SAAS,GACP,IAAI,CA+BhB;AAGD;;;;;;;GAOG;AACF,iDANU,KAAK,UACL,MAAM,iBACN,MAAM,oBACN,OAAO,GACL,IAAI,CAchB;AAGD;;;;;;GAMG;AACF,oCALU,KAAK,UACL,MAAM,oBACN,OAAO,GACL,IAAI,CAkBhB;AAED;;;;;GAKG;AACH,sCAJW,KAAK,kBACL,MAAM,EAAE,QA8BlB;AAID;;;;;;;;;;;;;GAaG;AACH,mCAZW,KAAK,kBACL,MAAM,EAAE,iBAKR,YAAY,qBAEV,cAAc,GAEd,MAAM,EAAE,CAwCpB;AAGD;;;;;;;;;GASG;AACH,iCARW,KAAK,kBACL,MAAM,EAAE,GAKN,MAAM,GAAC,IAAI,CAIvB;AA8BD;;;;GAIG;AACH,iCAHW,KAAK,MACL,cAAc,QA+BxB;AAED;;;;GAIG;AACH,sCAHW,KAAK,MACL,MAAM,QAUhB;AAGD;;;;GAIG;AACH,mCAHW,KAAK,MACL,MAAM,QAUhB;AAED;;;;GAIG;AACH,uCAHW,KAAK,MACL,MAAM,QAUhB;AAGD;;;;GAIG;AACH,iCAHW,KAAK,MACL,MAAM,QAUhB;AAED;;;;GAIG;AACH,8BAHW,KAAK,MACL,MAAM,QAUhB;AAED;;;;GAIG;AACH,kCAHW,KAAK,MACL,MAAM,QAUhB;AA2GD;;;GAGG;AACH,+BAFW,KAAK,QAkDf;;;;;;;;;;;;;;;;;;;;;;;;;2BAjsBa,OAAO,GAAG,SAAS;6BAInB,MAAM,EAAE;wBAIR,GAAG;qBAIJ;IACZ,CAAK,GAAG,EAAE,MAAM,GAAI,SAAS,CAAA;CAC1B;iCAIU,MAAM,EAAE;mCAIR,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI;;uBAKxB,oBAAoB;oBACpB,oBAAoB;wBACpB,oBAAoB;kBACpB,oBAAoB;eACpB,oBAAoB;mBACpB,oBAAoB;;6BAIjB;IACZ,CAAK,KAAK,EAAE,KAAK,GAAK,MAAM,CAAA;CACzB;;0BASS;IAAE,CAAE,GAAG,EAAE,MAAM,GAAI,QAAQ,CAAA;CAAE;;WAKhC,WAAW;aACX,WAAW;;wBAIR;IAAE,CAAE,QAAQ,EAAE,MAAM,GAAI,kBAAkB,CAAA;CAAE;;cAK/C,MAAM,EAAE;;;;gBACR,MAAM,EAAE;;;iBAKR,MAAM;;;;oBACN;QAAE,CAAE,GAAG,EAAE,MAAM,GAAI,MAAM,CAAA;KAAE;;;;2BAC3B;QAAE,CAAE,GAAG,EAAE,MAAM,GAAI,MAAM,CAAA;KAAE;aAC3B;QACL,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE;YACb,CAAQ,GAAG,EAAE,MAAM,GAAI,MAAM,CAAC;SACzB,CAAC;KACH,EAAE;;;;;mBACI,MAAM;;;;kBAEN,MAAM;;;cAKN,MAAM,EAAE;aACR,SAAS;aACT,MAAM,EAAE;eACR,iBAAiB;sBACjB,kBAAkB;WAClB,UAAU"} \ No newline at end of file