8.2 KiB
Entity API Reference
Complete API documentation for GPUI's entity system.
Entity Types
Entity
A strong reference to state of type T.
Methods:
entity_id()→EntityId- Returns unique identifierdowngrade()→WeakEntity<T>- Creates weak referenceread(cx)→&T- Immutable access to stateread_with(cx, |state, cx| ...)→R- Read with closure, returns closure resultupdate(cx, |state, cx| ...)→R- Mutable update withContext<T>, returns closure resultupdate_in(cx, |state, window, cx| ...)→R- Update withWindowaccess (requiresAsyncWindowContextorVisualTestContext)
Important Notes:
- Trying to update an entity while it's already being updated will panic
- Within closures, use the inner
cxprovided to avoid multiple borrow issues - With async contexts, return values are wrapped in
anyhow::Result
WeakEntity
A weak reference to state of type T.
Methods:
upgrade()→Option<Entity<T>>- Convert to strong reference if still aliveread_with(cx, |state, cx| ...)→Result<R>- Read if entity existsupdate(cx, |state, cx| ...)→Result<R>- Update if entity existsupdate_in(cx, |state, window, cx| ...)→Result<R>- Update with window if entity exists
Use Cases:
- Avoid circular dependencies between entities
- Store references in closures/callbacks without preventing cleanup
- Optional relationships between components
Important: All operations return Result since the entity may no longer exist.
AnyEntity
Dynamically-typed entity handle for storing entities of different types.
AnyWeakEntity
Dynamically-typed weak entity handle.
Entity Creation
cx.new()
Create new entity with initial state.
let entity = cx.new(|cx| MyState {
count: 0,
name: "Default".to_string(),
});
Parameters:
cx: &mut Appor other context type- Closure receiving
&mut Context<T>returning initial stateT
Returns: Entity<T>
Entity Operations
Reading State
read()
Direct read-only access to state.
let count = my_entity.read(cx).count;
Use when: Simple field access, no context operations needed.
read_with()
Read with context access in closure.
let count = my_entity.read_with(cx, |state, cx| {
// Can access both state and context
state.count
});
// Return multiple values
let (count, theme) = my_entity.read_with(cx, |state, cx| {
(state.count, cx.theme().clone())
});
Use when: Need context operations, multiple return values, complex logic.
Updating State
update()
Mutable update with Context<T>.
my_entity.update(cx, |state, cx| {
state.count += 1;
cx.notify(); // Trigger re-render
});
Available Operations:
cx.notify()- Trigger re-rendercx.entity()- Get current entitycx.emit(event)- Emit eventcx.spawn(task)- Spawn async task- Other
Context<T>methods
update_in()
Update with both Window and Context<T> access.
my_entity.update_in(cx, |state, window, cx| {
state.focused = window.is_window_focused();
cx.notify();
});
Requires: AsyncWindowContext or VisualTestContext
Use when: Need window-specific operations like focus state, window bounds, etc.
Context Methods for Entities
cx.entity()
Get current entity being updated.
impl MyComponent {
fn some_method(&mut self, cx: &mut Context<Self>) {
let current_entity = cx.entity(); // Entity<MyComponent>
let weak = current_entity.downgrade();
}
}
cx.observe()
Observe entity for changes.
cx.observe(&entity, |this, observed_entity, cx| {
// Called when observed_entity.update() calls cx.notify()
println!("Entity changed");
}).detach();
Returns: Subscription - Call .detach() to make permanent
cx.subscribe()
Subscribe to events from entity.
cx.subscribe(&entity, |this, emitter, event: &SomeEvent, cx| {
// Called when emitter emits SomeEvent
match event {
SomeEvent::DataChanged => {
cx.notify();
}
}
}).detach();
Returns: Subscription - Call .detach() to make permanent
cx.observe_new_entities()
Register callback for new entities of a type.
cx.observe_new_entities::<MyState>(|entity, cx| {
println!("New entity created: {:?}", entity.entity_id());
}).detach();
Async Operations
cx.spawn()
Spawn foreground task (UI thread).
cx.spawn(async move |this, cx| {
// `this`: WeakEntity<T>
// `cx`: &mut AsyncApp
let result = some_async_work().await;
// Update entity safely
let _ = this.update(cx, |state, cx| {
state.data = result;
cx.notify();
});
}).detach();
Note: Always use weak entity reference in spawned tasks to prevent retain cycles.
cx.background_spawn()
Spawn background task (background thread).
cx.background_spawn(async move {
// Long-running computation
let result = heavy_computation().await;
// Cannot directly update entities here
// Use channels or spawn foreground task to update
}).detach();
Entity Lifecycle
Creation
Entities are created via cx.new() and immediately registered in the app.
Reference Counting
Entity<T>is a strong reference (increases reference count)WeakEntity<T>is a weak reference (does not increase reference count)- Cloning
Entity<T>increases reference count
Disposal
Entities are automatically disposed when all strong references are dropped.
{
let entity = cx.new(|cx| MyState::default());
// entity exists
} // entity dropped here if no other strong references exist
Memory Leak Prevention:
- Use
WeakEntityin closures/callbacks - Use
WeakEntityfor parent-child relationships - Avoid circular strong references
EntityId
Every entity has a unique identifier.
let id: EntityId = entity.entity_id();
// EntityIds can be compared
if entity1.entity_id() == entity2.entity_id() {
// Same entity
}
Use Cases:
- Debugging and logging
- Entity comparison without borrowing
- Hash maps keyed by entity
Error Handling
WeakEntity Operations
All WeakEntity operations return Result:
let weak = entity.downgrade();
// Handle potential failure
match weak.read_with(cx, |state, cx| state.count) {
Ok(count) => println!("Count: {}", count),
Err(e) => eprintln!("Entity no longer exists: {}", e),
}
// Or use Result combinators
let _ = weak.update(cx, |state, cx| {
state.count += 1;
cx.notify();
}).ok(); // Ignore errors
Update Panics
Nested updates on the same entity will panic:
// ❌ Will panic
entity.update(cx, |state1, cx| {
entity.update(cx, |state2, cx| {
// Panic: entity already borrowed
});
});
Solution: Perform updates sequentially or use different entities.
Type Conversions
Entity → WeakEntity
let entity: Entity<T> = cx.new(|cx| T::default());
let weak: WeakEntity<T> = entity.downgrade();
WeakEntity → Entity
let weak: WeakEntity<T> = entity.downgrade();
let strong: Option<Entity<T>> = weak.upgrade();
AnyEntity
let any: AnyEntity = entity.into();
let typed: Option<Entity<T>> = any.downcast::<T>();
Best Practice Guidelines
Always Use Inner cx
// ✅ Good: Use inner cx
entity.update(cx, |state, inner_cx| {
inner_cx.notify(); // Use inner_cx, not outer cx
});
// ❌ Bad: Use outer cx
entity.update(cx, |state, inner_cx| {
cx.notify(); // Wrong! Multiple borrow error
});
Weak References in Closures
// ✅ Good: Weak reference
let weak = cx.entity().downgrade();
callback(move || {
let _ = weak.update(cx, |state, cx| {
cx.notify();
});
});
// ❌ Bad: Strong reference (retain cycle)
let strong = cx.entity();
callback(move || {
strong.update(cx, |state, cx| {
// May never be dropped
cx.notify();
});
});
Sequential Updates
// ✅ Good: Sequential updates
entity1.update(cx, |state, cx| { /* ... */ });
entity2.update(cx, |state, cx| { /* ... */ });
// ❌ Bad: Nested updates
entity1.update(cx, |_, cx| {
entity2.update(cx, |_, cx| {
// May panic if entities are related
});
});