bevy_entity_ptr
Ergonomic smart-pointer-like access to Bevy ECS entities with immutable-only semantics.
Overview
This crate provides two complementary approaches for accessing entity data in Bevy:
| Type | Safety | Ergonomics | Use When |
|---|---|---|---|
EntityHandle |
✅ Fully safe | Explicit world param | Store in components |
BoundEntity<'w> |
✅ Fully safe | Scoped lifetime | Simple access, compiler-checked |
EntityPtr |
✅ Safe API* | No lifetime params | Tree/graph traversal, recursion |
*One internal unsafe hidden by WorldExt extension trait
Recommendation: Start with BoundEntity<'w>. Use EntityPtr when lifetime
annotations become cumbersome for complex traversal.
Design Principles
- Immutable only - No
get_mutvariants (functional programming style) - Safe by default -
WorldExttrait hides the internal unsafe, users never writeunsafeblocks - Graceful stale handling - Despawned entities return
None, not undefined behavior - Zero-cost where possible -
#[repr(transparent)],#[inline],const fn
Installation
Add to your Cargo.toml:
[]
= "0.5"
Quick Start
Safe Approach: EntityHandle + BoundEntity
Use this when you want fully safe code with explicit world parameters:
use *;
use ;
;
;
Ergonomic Approach: WorldExt + EntityPtr
Use this when you want fluent traversal without passing &World everywhere.
The WorldExt extension trait hides the internal unsafe, so you never need to write unsafe blocks.
use *;
use ;
;
;
;
// Recursive tree traversal - no &World parameter needed!
// Find the root by traversing parents
Mixed Usage
Store handles in components, use smart pointers for traversal:
use *;
use ;
// EntityHandle is Send + Sync, safe to store in components
;
Navigation Traits (Optional)
Enable the nav-traits feature for parent/child navigation helpers:
[]
= { = "0.5", = ["nav-traits"] }
use *;
use ;
;
;
Thread Safety
| Type | Send | Sync | Notes |
|---|---|---|---|
EntityHandle |
Yes | Yes | Safe to store in components |
BoundEntity<'w> |
No | No | Borrows &World |
WorldRef |
No | No | System-scoped only |
EntityPtr |
No | No | System-scoped only |
Using EntityPtr in Collections
EntityPtr implements Eq and Hash, allowing use in HashSet and HashMap:
use HashSet;
use *;
use WorldExt;
Note: EntityPtr comparison uses entity ID only, assuming same-world context (the typical usage pattern).
Multi-Threaded Usage Example
Multiple read-only systems can use bevy_entity_ptr concurrently. Bevy's scheduler runs them in parallel when all systems only read:
use *;
use ;
;
;
;
;
/// System A: Computes total health across hierarchies
/// System B: Runs concurrently with System A
// Bevy's scheduler runs both systems in parallel - both only read
Why this is safe:
WorldExt::entity_ptr()hides the internal unsafe - you never writeunsafeblocksEntityPtris NOTSend/Sync- it cannot escape to other threads- Bevy's scheduler detects that both systems only have
&Worldaccess and runs them in parallel - All operations through
EntityPtrare read-only by design
See examples/concurrent_systems.rs for a complete runnable example.
Stale Reference Handling
Both approaches gracefully handle despawned entities:
use *;
use EntityHandle;
;
Safety
For most users: The WorldExt extension trait (world.entity_ptr(entity)) hides all unsafe code. You never need to write unsafe blocks.
For advanced users: If you need direct access to WorldRef::new(), the caller must ensure:
- The
Worldoutlives allEntityPtrinstances created from theWorldRef - The
Worldis NOT mutated while anyEntityPtrexists
In Bevy systems, this is naturally satisfied: systems with &World access cannot mutate.
What This Crate Does NOT Support (By Design)
- Mutable access - Use Bevy's native APIs for mutations
- Despawning - Use
world.despawn()directly - Component insertion/removal - Use Bevy's native APIs
- Cross-frame storage of
EntityPtr- UseEntityHandleor rawEntityfor storage
Bevy Compatibility
bevy_entity_ptr |
Bevy |
|---|---|
| 0.5 | 0.18 |
| 0.4 | 0.17 |
| 0.3 | 0.16 |
| 0.2 | 0.15 |
| 0.1 | 0.15 |
Development
This crate is co-developed with Claude Code.
License
MIT