Skip to content

Conversation

@jakeklassen
Copy link
Owner

@jakeklassen jakeklassen commented Dec 19, 2025

Performance: Array + Map entity storage

Replace Set with EntityCollection backed by:

  • Array for 2x faster iteration
  • Map<Entity, index> for O(1) lookups
  • Swap-and-pop for O(1) removal

Benchmarks

Benchmark Before After Change
simple_iter 80,937 163,000 +101%
packed_5 139,836 157,000 +12%
frag_iter 44,291 50,000 +13%
add_remove 21,209 21,500 +1%
entity_cycle 7,556 4,000 -47%*

*entity_cycle regressed due to Map overhead but remains 77% faster than miniplex.

Also includes

  • addEntityComponents overload for batch operations
  • has(value: unknown) signature for flexible membership checks
  • Some JSDoc tweaks
  • Update release.yml

Summary by CodeRabbit

  • New Features

    • Improved entity API with a new collection type and expanded addEntityComponents overloads for single or multiple components.
    • Utility now accepts any iterable for selecting pickable enemies (more input flexibility).
  • Documentation

    • Added runtime benchmarks, performance plan, and hierarchy design notes.
    • Added JSDoc annotations to benchmark cases.
  • Infrastructure

    • CI updated to use OIDC-based token handling.
  • Other

    • .gitignore now ignores tmp/ and tracks .vscode/settings.json.

✏️ Tip: You can customize this high-level summary in your review settings.

@netlify
Copy link

netlify bot commented Dec 19, 2025

Deploy Preview for objecs ready!

Name Link
🔨 Latest commit 3ea6532
🔍 Latest deploy log https://app.netlify.com/projects/objecs/deploys/694566f6b820fd0008469508
😎 Deploy Preview https://deploy-preview-1--objecs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link

coderabbitai bot commented Dec 19, 2025

Warning

Rate limit exceeded

@jakeklassen has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 2 minutes and 18 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 36f86c9 and 12d8169.

📒 Files selected for processing (1)
  • packages/objecs/package.json (1 hunks)

Walkthrough

Replaces Set-based entity storage with a new array-backed EntityCollection (swap-and-pop removal), updates World and Archetype to use ReadonlyEntityCollection/EntityCollection, expands addEntityComponents overloads, adds docs/benchmarks, CI workflow OIDC auth, .gitignore tweaks, several JSDoc updates, and related test adjustments.

Changes

Cohort / File(s) Summary
CI/CD & Repo config
.github/workflows/release.yml, .gitignore
Added permissions for OIDC id-token write and repo contents read in release workflow; removed NODE_AUTH_TOKEN env usage for pnpm publish. Added tmp/ to .gitignore and un-ignored !.vscode/settings.json.
Core EntityCollection implementation
packages/objecs/src/world.ts
Introduced ReadonlyEntityCollection<T> and EntityCollection<T> (array + Map, swap-and-pop removal, iterable helpers). Replaced World’s Set storage with EntityCollection; adjusted create/delete/clear and introduced overloads and implementation changes for addEntityComponents.
Archetype migration
packages/objecs/src/archetype.ts
Replaced internal #entities Set with EntityCollection; updated constructor, getter return type, add/remove/clear paths to use EntityCollection API and adjusted internal casts.
Tests updated for collection semantics
packages/objecs/src/world.test.ts, packages/objecs/src/archetype.test.ts
Updated imports and expectations to use ReadonlyEntityCollection; added tests for multi-component addEntityComponents behavior and error cases; adapted assertions to size/has and iteration via spread/array.
Examples: API flexibility change
packages/examples/src/demos/shmup/enemy/determine-pickable-enemies.ts
Changed function parameter type from ReadonlySet<T> to Iterable<T> and updated internal materialization from Array.from() to spread ([...entities]) while preserving filtering logic.
Benchmarks & JSDoc
benchmark.md, packages/ecs-benchmark/src/cases/miniplex/*
Added runtime benchmark results and comparison table to benchmark.md. Added JSDoc parameter annotations to miniplex case files (add_remove.js, entity_cycle.js, frag_iter.js, packed_5.js, simple_iter.js).
Design & planning docs
hierarchy-ideas.md, performance-plan.md
Added hierarchy ideation document (three approaches, rollout) and a performance improvement plan describing architecture comparisons, phased refactor strategy, Phase 1 results, and tradeoffs from the EntityCollection migration.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Areas to focus on:

  • packages/objecs/src/world.ts: correctness of EntityCollection (add/remove/has/index map), swap-and-pop removal, iterator behavior, and addEntityComponents overloads/logic.
  • packages/objecs/src/archetype.ts: integration points where archetype membership is managed; ensure no behavioral regressions.
  • Tests: updated assertions (size/has/iteration) to validate EntityCollection semantics.
  • Example and docs: confirm API signature changes (Iterable) are consistent with call sites.

Poem

🐰 I hopped through Sets and found a faster way,
Swapped to arrays and watched the ticks obey.
Map kept indices, pop sent gaps away,
Archetypes nodded and tests learned to play.
Benchmarks whisper tales of work begun today. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: EntityCollection + batch component operations' clearly summarizes the main changes: introducing a new EntityCollection type and adding batch component operation capabilities, which aligns with the PR's core objectives of performance optimization and API expansion.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (5)
.gitignore (1)

5-5: Consider using directory-specific pattern.

The rule tmp will match both files and directories named tmp. If only temporary directories should be ignored, use tmp/ for clarity and to prevent accidentally ignoring files named tmp.

-tmp
+tmp/
.github/workflows/release.yml (1)

31-31: Consider adding --provenance flag for OIDC publishing.

When using OIDC authentication with npm, the --provenance flag is typically required to generate signed provenance attestations. This flag links the published package to its source and build, improving supply chain security.

🔎 Suggested change
-      - run: pnpm publish --no-git-checks --access public --filter objecs
+      - run: pnpm publish --no-git-checks --access public --provenance --filter objecs
performance-plan.md (2)

5-22: Add language specifiers to code blocks for better rendering.

The code blocks displaying benchmark results should include a language identifier (e.g., text or plaintext) for proper syntax highlighting and rendering.

🔎 Proposed fix
-```
+```text
 > ecs-benchmark@ start
 > node src/bench.js objecs miniplex

Apply the same change to the code blocks at lines 111-118 and 121-128.

Also applies to: 111-118, 121-128


131-138: Add blank lines around the comparison table.

The table at line 131 should be surrounded by blank lines for proper markdown rendering and better readability.

🔎 Proposed fix
 **Comparison with miniplex after Phase 1:**
+
 | Benchmark | objecs | miniplex | Diff |
 |-----------|--------|----------|------|
packages/objecs/src/world.ts (1)

222-274: Well-designed overloads for batch operations.

The overloads for addEntityComponents provide excellent DX:

  • Single-component calls: addEntityComponents(entity, "position", { x: 0, y: 0 })
  • Batch updates: addEntityComponents(entity, { position: { x: 0, y: 0 }, velocity: { x: 1, y: 1 } })

The batch operation is more efficient as it updates archetype membership only once. The implementation correctly handles both cases and maintains type safety through overloads.

Note: The @ts-ignore comments at lines 254 and 261 bypass type checking. While this is pragmatic given the dynamic nature of the operation, consider if there's a type-safe alternative using mapped types or conditional types.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f90cb2 and 97ba8a6.

📒 Files selected for processing (15)
  • .github/workflows/release.yml (1 hunks)
  • .gitignore (1 hunks)
  • benchmark.md (1 hunks)
  • hierarchy-ideas.md (1 hunks)
  • packages/ecs-benchmark/src/cases/miniplex/add_remove.js (1 hunks)
  • packages/ecs-benchmark/src/cases/miniplex/entity_cycle.js (1 hunks)
  • packages/ecs-benchmark/src/cases/miniplex/frag_iter.js (1 hunks)
  • packages/ecs-benchmark/src/cases/miniplex/packed_5.js (1 hunks)
  • packages/ecs-benchmark/src/cases/miniplex/simple_iter.js (1 hunks)
  • packages/examples/src/demos/shmup/enemy/determine-pickable-enemies.ts (1 hunks)
  • packages/objecs/src/archetype.test.ts (6 hunks)
  • packages/objecs/src/archetype.ts (6 hunks)
  • packages/objecs/src/world.test.ts (4 hunks)
  • packages/objecs/src/world.ts (6 hunks)
  • performance-plan.md (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
packages/objecs/src/world.test.ts (2)
packages/objecs/src/world.ts (2)
  • ReadonlyEntityCollection (13-27)
  • entities (149-151)
packages/objecs/src/archetype.ts (1)
  • entities (45-49)
packages/objecs/src/archetype.ts (1)
packages/objecs/src/world.ts (4)
  • EntityCollection (33-136)
  • SafeEntity (4-7)
  • entities (149-151)
  • ReadonlyEntityCollection (13-27)
packages/objecs/src/archetype.test.ts (1)
packages/objecs/src/world.ts (3)
  • EntityCollection (33-136)
  • ReadonlyEntityCollection (13-27)
  • SafeEntity (4-7)
🪛 LanguageTool
benchmark.md

[grammar] ~4-~4: Ensure spelling is correct
Context: ...rk > node src/bench.js objecs miniplex objecs packed_5 139,836 op/s simple_it...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~21-~21: Ensure spelling is correct
Context: ... | | ---- | --: |--: |--: |--: |--: | | objecs | 139,836 | 80,937 | 44,291 | 7,556 | 2...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

hierarchy-ideas.md

[grammar] ~232-~232: Ensure spelling is correct
Context: ...rchy management - Could be published as objecs-hierarchy package Cons: - Additio...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~275-~275: Ensure spelling is correct
Context: ...d potentially be: - A separate package (objecs-hierarchy) - Built into objECS core wi...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

performance-plan.md

[grammar] ~1-~1: Ensure spelling is correct
Context: # objecs Performance Improvement Plan ## Benchm...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~34-~34: Ensure spelling is correct
Context: ...y Architectural Differences | Aspect | objecs | miniplex | |--------|--------|-------...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 markdownlint-cli2 (0.18.1)
performance-plan.md

5-5: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


111-111: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


121-121: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


131-131: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

🔇 Additional comments (17)
hierarchy-ideas.md (1)

1-287: Excellent design document—clear, well-structured, and future-proof.

This comprehensive walkthrough of hierarchy implementation approaches aligns nicely with the EntityCollection-based improvements in this PR. The progression from simple (Approach 1) to sophisticated (Approach 3) gives users a pragmatic adoption path, and the code examples correctly use the new addEntityComponents() overload and ReadonlySet patterns that align with the library's direction.

The open questions section shows good architectural thinking about synchronization and lifecycle management. The references to battle-tested implementations (Wicked Engine, Bevy, Unity) lend credibility.

packages/examples/src/demos/shmup/enemy/determine-pickable-enemies.ts (1)

7-9: LGTM! Clean refactoring to support the new EntityCollection API.

The changes align well with the PR objectives:

  • Widening the parameter type from ReadonlySet<T> to Iterable<T> maintains backwards compatibility while supporting the new EntityCollection
  • Using spread syntax [...entities] is idiomatic and functionally equivalent to Array.from(entities)
packages/ecs-benchmark/src/cases/miniplex/entity_cycle.js (1)

4-6: LGTM! Documentation improvements.

The JSDoc additions across all miniplex benchmark files improve type documentation without affecting runtime behavior.

benchmark.md (1)

1-22: LGTM! Benchmark documentation added.

The benchmark results clearly document performance improvements across most test cases, aligning with the PR objectives. The static analysis spelling warnings for "objecs" and "miniplex" are false positives (these are package names).

packages/objecs/src/archetype.test.ts (3)

2-7: LGTM! Updated imports for EntityCollection API.

The imports correctly include EntityCollection and ReadonlyEntityCollection to support the migration from Set-based storage.


35-35: LGTM! Archetype construction updated for EntityCollection.

The test correctly instantiates EntityCollection() for the Archetype constructor, aligning with the new storage implementation.

Also applies to: 54-54, 67-67


44-44: LGTM! Type expectations updated to ReadonlyEntityCollection.

The type expectations correctly reflect the new public API returning ReadonlyEntityCollection instead of ReadonlySet.

Also applies to: 109-111

packages/objecs/src/world.test.ts (5)

2-2: LGTM! Import updated for EntityCollection API.

The import correctly includes ReadonlyEntityCollection for the migration from Set-based storage.


32-34: LGTM! Type expectation updated for world.entities.

The type correctly expects ReadonlyEntityCollection instead of ReadonlySet, aligning with the new collection API.


160-202: LGTM! Comprehensive test coverage for batch component addition.

The new test suite thoroughly validates the batch addEntityComponents() overload:

  • Verifies multiple components are added correctly
  • Confirms archetype membership updates
  • Tests error handling for non-existent entities

211-215: LGTM! Archetype test assertions updated for EntityCollection.

The test assertions correctly use .size and .has() methods compatible with the ReadonlyEntityCollection interface.

Also applies to: 223-223, 230-231, 235-235


289-299: LGTM! Entity iteration updated for array-backed collection.

The spread operator correctly converts the iterable EntityCollection to an array for indexed access, enabling deterministic assertions on specific entities.

Also applies to: 305-309

packages/objecs/src/archetype.ts (1)

113-130: LGTM - Logic correctly filters entities.

The without method correctly creates a new archetype by filtering entities that don't have the excluded components. The iteration and filtering logic is sound.

packages/objecs/src/world.ts (4)

9-27: LGTM - Well-designed readonly interface.

The ReadonlyEntityCollection interface provides a clean readonly API surface. The has(value: unknown) signature is intentionally flexible (as documented) to allow membership checks without type narrowing, which is appropriate for ECS use cases.


33-43: LGTM - Efficient dual-structure storage.

The combination of an array (fast iteration) and a Map (O(1) lookups) achieves the performance goals outlined in the PR. The implementation is clean and properly encapsulated.


63-81: LGTM - Correct swap-and-pop implementation.

The swap-and-pop removal logic is correctly implemented:

  1. O(1) deletion by swapping the target with the last element
  2. Properly updates the indices map for the swapped element
  3. Handles the edge case where the target is already the last element

This achieves the O(1) removal performance goal while maintaining array compactness.


97-114: LGTM - Correctly mimics Set.entries() behavior.

The entries() method returns [entity, entity] tuples to maintain API compatibility with Set.entries(), which duplicates values to match the Map interface. The custom iterator implementation avoids creating intermediate arrays for better performance.

@jakeklassen jakeklassen changed the title feat: some performance gains feat: EntityCollection + batch component operations Dec 19, 2025
@jakeklassen jakeklassen merged commit c18fd6f into main Dec 19, 2025
8 checks passed
@jakeklassen jakeklassen deleted the feat/perf branch December 19, 2025 14:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants