add gpui skills

This commit is contained in:
2026-03-08 08:41:46 +07:00
parent 34a32a1bd8
commit aec32e450a
21 changed files with 7057 additions and 0 deletions

View File

@@ -0,0 +1,168 @@
---
name: gpui-entity
description: Entity management and state handling in GPUI. Use when working with entities, managing component state, coordinating between components, handling async operations with state updates, or implementing reactive patterns. Entities provide safe concurrent access to application state.
---
## Overview
An `Entity<T>` is a handle to state of type `T`, providing safe access and updates.
**Key Methods:**
- `entity.read(cx)``&T` - Read-only access
- `entity.read_with(cx, |state, cx| ...)``R` - Read with closure
- `entity.update(cx, |state, cx| ...)``R` - Mutable update
- `entity.downgrade()``WeakEntity<T>` - Create weak reference
- `entity.entity_id()``EntityId` - Unique identifier
**Entity Types:**
- **`Entity<T>`**: Strong reference (increases ref count)
- **`WeakEntity<T>`**: Weak reference (doesn't prevent cleanup, returns `Result`)
## Quick Start
### Creating and Using Entities
```rust
// Create entity
let counter = cx.new(|cx| Counter { count: 0 });
// Read state
let count = counter.read(cx).count;
// Update state
counter.update(cx, |state, cx| {
state.count += 1;
cx.notify(); // Trigger re-render
});
// Weak reference (for closures/callbacks)
let weak = counter.downgrade();
let _ = weak.update(cx, |state, cx| {
state.count += 1;
cx.notify();
});
```
### In Components
```rust
struct MyComponent {
shared_state: Entity<SharedData>,
}
impl MyComponent {
fn new(cx: &mut App) -> Entity<Self> {
let shared = cx.new(|_| SharedData::default());
cx.new(|cx| Self {
shared_state: shared,
})
}
fn update_shared(&mut self, cx: &mut Context<Self>) {
self.shared_state.update(cx, |state, cx| {
state.value = 42;
cx.notify();
});
}
}
```
### Async Operations
```rust
impl MyComponent {
fn fetch_data(&mut self, cx: &mut Context<Self>) {
let weak_self = cx.entity().downgrade();
cx.spawn(async move |cx| {
let data = fetch_from_api().await;
// Update entity safely
let _ = weak_self.update(cx, |state, cx| {
state.data = Some(data);
cx.notify();
});
}).detach();
}
}
```
## Core Principles
### Always Use Weak References in Closures
```rust
// ✅ Good: Weak reference prevents retain cycles
let weak = cx.entity().downgrade();
callback(move || {
let _ = weak.update(cx, |state, cx| cx.notify());
});
// ❌ Bad: Strong reference may cause memory leak
let strong = cx.entity();
callback(move || {
strong.update(cx, |state, cx| cx.notify());
});
```
### Use Inner Context
```rust
// ✅ Good: Use inner cx from closure
entity.update(cx, |state, inner_cx| {
inner_cx.notify(); // Correct
});
// ❌ Bad: Use outer cx (multiple borrow error)
entity.update(cx, |state, inner_cx| {
cx.notify(); // Wrong!
});
```
### Avoid Nested Updates
```rust
// ✅ Good: Sequential updates
entity1.update(cx, |state, cx| { /* ... */ });
entity2.update(cx, |state, cx| { /* ... */ });
// ❌ Bad: Nested updates (may panic)
entity1.update(cx, |_, cx| {
entity2.update(cx, |_, cx| { /* ... */ });
});
```
## Common Use Cases
1. **Component State**: Internal state that needs reactivity
2. **Shared State**: State shared between multiple components
3. **Parent-Child**: Coordinating between related components (use weak refs)
4. **Async State**: Managing state that changes from async operations
5. **Observations**: Reacting to changes in other entities
## Reference Documentation
### Complete API Documentation
- **Entity API**: See [api-reference.md](references/api-reference.md)
- Entity types, methods, lifecycle
- Context methods, async operations
- Error handling, type conversions
### Implementation Guides
- **Patterns**: See [patterns.md](references/patterns.md)
- Model-view separation, state management
- Cross-entity communication, async operations
- Observer pattern, event subscription
- Pattern selection guide
- **Best Practices**: See [best-practices.md](references/best-practices.md)
- Avoiding common pitfalls, memory leaks
- Performance optimization, batching updates
- Lifecycle management, cleanup
- Async best practices, testing
- **Advanced Patterns**: See [advanced.md](references/advanced.md)
- Entity collections, registry pattern
- Debounced/throttled updates, state machines
- Entity snapshots, transactions, pools

View File

@@ -0,0 +1,528 @@
# Advanced Entity Patterns
Advanced techniques for sophisticated entity management scenarios.
## Entity Collections Management
### Dynamic Collection with Cleanup
```rust
struct EntityCollection<T> {
strong_refs: Vec<Entity<T>>,
weak_refs: Vec<WeakEntity<T>>,
}
impl<T> EntityCollection<T> {
fn new() -> Self {
Self {
strong_refs: Vec::new(),
weak_refs: Vec::new(),
}
}
fn add(&mut self, entity: Entity<T>, cx: &mut App) {
self.strong_refs.push(entity.clone());
self.weak_refs.push(entity.downgrade());
}
fn remove(&mut self, entity_id: EntityId, cx: &mut App) {
self.strong_refs.retain(|e| e.entity_id() != entity_id);
self.weak_refs.retain(|w| {
w.upgrade()
.map(|e| e.entity_id() != entity_id)
.unwrap_or(false)
});
}
fn cleanup_invalid(&mut self, cx: &mut App) {
self.weak_refs.retain(|weak| weak.upgrade().is_some());
}
fn for_each<F>(&self, cx: &mut App, mut f: F)
where
F: FnMut(&Entity<T>, &mut App),
{
for entity in &self.strong_refs {
f(entity, cx);
}
}
fn for_each_weak<F>(&mut self, cx: &mut App, mut f: F)
where
F: FnMut(Entity<T>, &mut App),
{
self.weak_refs.retain(|weak| {
if let Some(entity) = weak.upgrade() {
f(entity, cx);
true
} else {
false // Remove invalid weak references
}
});
}
}
```
### Entity Registry Pattern
```rust
use std::collections::HashMap;
struct EntityRegistry<T> {
entities: HashMap<EntityId, WeakEntity<T>>,
}
impl<T> EntityRegistry<T> {
fn new() -> Self {
Self {
entities: HashMap::new(),
}
}
fn register(&mut self, entity: &Entity<T>) {
self.entities.insert(entity.entity_id(), entity.downgrade());
}
fn unregister(&mut self, entity_id: EntityId) {
self.entities.remove(&entity_id);
}
fn get(&self, entity_id: EntityId) -> Option<Entity<T>> {
self.entities.get(&entity_id)?.upgrade()
}
fn cleanup(&mut self) {
self.entities.retain(|_, weak| weak.upgrade().is_some());
}
fn count(&self) -> usize {
self.entities.len()
}
fn all_entities(&self) -> Vec<Entity<T>> {
self.entities
.values()
.filter_map(|weak| weak.upgrade())
.collect()
}
}
```
## Conditional Update Patterns
### Debounced Updates
```rust
use std::time::{Duration, Instant};
struct DebouncedEntity<T> {
entity: Entity<T>,
last_update: Instant,
debounce_duration: Duration,
pending_update: Option<Box<dyn FnOnce(&mut T, &mut Context<T>)>>,
}
impl<T: 'static> DebouncedEntity<T> {
fn new(entity: Entity<T>, debounce_ms: u64) -> Self {
Self {
entity,
last_update: Instant::now(),
debounce_duration: Duration::from_millis(debounce_ms),
pending_update: None,
}
}
fn update<F>(&mut self, cx: &mut App, update_fn: F)
where
F: FnOnce(&mut T, &mut Context<T>) + 'static,
{
let now = Instant::now();
let elapsed = now.duration_since(self.last_update);
if elapsed >= self.debounce_duration {
// Execute immediately
self.entity.update(cx, update_fn);
self.last_update = now;
self.pending_update = None;
} else {
// Store for later
self.pending_update = Some(Box::new(update_fn));
// Schedule execution
let entity = self.entity.clone();
let delay = self.debounce_duration - elapsed;
cx.spawn(async move |cx| {
tokio::time::sleep(delay).await;
if let Some(update) = self.pending_update.take() {
entity.update(cx, |state, inner_cx| {
update(state, inner_cx);
});
}
}).detach();
}
}
}
```
### Throttled Updates
```rust
struct ThrottledEntity<T> {
entity: Entity<T>,
last_update: Instant,
throttle_duration: Duration,
}
impl<T: 'static> ThrottledEntity<T> {
fn new(entity: Entity<T>, throttle_ms: u64) -> Self {
Self {
entity,
last_update: Instant::now(),
throttle_duration: Duration::from_millis(throttle_ms),
}
}
fn try_update<F>(&mut self, cx: &mut App, update_fn: F) -> bool
where
F: FnOnce(&mut T, &mut Context<T>),
{
let now = Instant::now();
let elapsed = now.duration_since(self.last_update);
if elapsed >= self.throttle_duration {
self.entity.update(cx, update_fn);
self.last_update = now;
true
} else {
false // Update throttled
}
}
}
```
## Entity State Machine Pattern
```rust
enum AppState {
Idle,
Loading,
Loaded(String),
Error(String),
}
struct StateMachine {
state: AppState,
}
impl StateMachine {
fn new() -> Self {
Self {
state: AppState::Idle,
}
}
fn start_loading(&mut self, cx: &mut Context<Self>) {
if matches!(self.state, AppState::Idle | AppState::Error(_)) {
self.state = AppState::Loading;
cx.notify();
let weak_entity = cx.entity().downgrade();
cx.spawn(async move |cx| {
let result = perform_load().await;
let _ = weak_entity.update(cx, |state, cx| {
match result {
Ok(data) => state.on_load_success(data, cx),
Err(e) => state.on_load_error(e.to_string(), cx),
}
});
}).detach();
}
}
fn on_load_success(&mut self, data: String, cx: &mut Context<Self>) {
if matches!(self.state, AppState::Loading) {
self.state = AppState::Loaded(data);
cx.notify();
}
}
fn on_load_error(&mut self, error: String, cx: &mut Context<Self>) {
if matches!(self.state, AppState::Loading) {
self.state = AppState::Error(error);
cx.notify();
}
}
fn reset(&mut self, cx: &mut Context<Self>) {
self.state = AppState::Idle;
cx.notify();
}
}
async fn perform_load() -> Result<String, anyhow::Error> {
// Actual load implementation
Ok("Data".to_string())
}
```
## Entity Proxy Pattern
```rust
struct EntityProxy<T> {
entity: WeakEntity<T>,
}
impl<T> EntityProxy<T> {
fn new(entity: &Entity<T>) -> Self {
Self {
entity: entity.downgrade(),
}
}
fn with<F, R>(&self, cx: &mut App, f: F) -> Result<R, anyhow::Error>
where
F: FnOnce(&T, &App) -> R,
{
self.entity.read_with(cx, f)
}
fn update<F, R>(&self, cx: &mut App, f: F) -> Result<R, anyhow::Error>
where
F: FnOnce(&mut T, &mut Context<T>) -> R,
{
self.entity.update(cx, f)
}
fn is_valid(&self, cx: &App) -> bool {
self.entity.upgrade().is_some()
}
}
```
## Cascading Updates Pattern
```rust
struct CascadingUpdater {
entities: Vec<WeakEntity<UpdateTarget>>,
}
impl CascadingUpdater {
fn new() -> Self {
Self {
entities: Vec::new(),
}
}
fn add_target(&mut self, entity: &Entity<UpdateTarget>) {
self.entities.push(entity.downgrade());
}
fn cascade_update<F>(&mut self, cx: &mut App, update_fn: F)
where
F: Fn(&mut UpdateTarget, &mut Context<UpdateTarget>) + Clone,
{
// Update all entities in sequence
self.entities.retain(|weak| {
if let Ok(_) = weak.update(cx, |state, inner_cx| {
update_fn.clone()(state, inner_cx);
}) {
true // Keep valid entity
} else {
false // Remove invalid entity
}
});
}
}
struct UpdateTarget {
value: i32,
}
```
## Entity Snapshot Pattern
```rust
use serde::{Serialize, Deserialize};
#[derive(Clone, Serialize, Deserialize)]
struct EntitySnapshot {
data: String,
timestamp: u64,
}
struct SnapshotableEntity {
data: String,
snapshots: Vec<EntitySnapshot>,
}
impl SnapshotableEntity {
fn new(data: String) -> Self {
Self {
data,
snapshots: Vec::new(),
}
}
fn take_snapshot(&mut self, cx: &mut Context<Self>) {
let snapshot = EntitySnapshot {
data: self.data.clone(),
timestamp: current_timestamp(),
};
self.snapshots.push(snapshot);
cx.notify();
}
fn restore_snapshot(&mut self, index: usize, cx: &mut Context<Self>) -> Result<(), String> {
if let Some(snapshot) = self.snapshots.get(index) {
self.data = snapshot.data.clone();
cx.notify();
Ok(())
} else {
Err("Invalid snapshot index".to_string())
}
}
fn clear_old_snapshots(&mut self, keep_last: usize, cx: &mut Context<Self>) {
if self.snapshots.len() > keep_last {
self.snapshots.drain(0..self.snapshots.len() - keep_last);
cx.notify();
}
}
}
fn current_timestamp() -> u64 {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
}
```
## Entity Transaction Pattern
```rust
struct Transaction<T> {
entity: Entity<T>,
original_state: Option<T>,
}
impl<T: Clone> Transaction<T> {
fn begin(entity: Entity<T>, cx: &mut App) -> Self {
let original_state = entity.read(cx).clone();
Self {
entity,
original_state: Some(original_state),
}
}
fn update<F>(&mut self, cx: &mut App, update_fn: F)
where
F: FnOnce(&mut T, &mut Context<T>),
{
self.entity.update(cx, update_fn);
}
fn commit(mut self, cx: &mut App) {
self.original_state = None; // Don't rollback
self.entity.update(cx, |_, cx| {
cx.notify();
});
}
fn rollback(mut self, cx: &mut App) {
if let Some(original) = self.original_state.take() {
self.entity.update(cx, |state, cx| {
*state = original;
cx.notify();
});
}
}
}
impl<T> Drop for Transaction<T> {
fn drop(&mut self) {
// Auto-rollback if not committed
if self.original_state.is_some() {
eprintln!("Warning: Transaction dropped without commit");
}
}
}
// Usage
fn perform_transaction(entity: Entity<MyState>, cx: &mut App) -> Result<(), String> {
let mut tx = Transaction::begin(entity, cx);
tx.update(cx, |state, cx| {
state.value = 42;
});
if validate_state(&tx.entity, cx)? {
tx.commit(cx);
Ok(())
} else {
tx.rollback(cx);
Err("Validation failed".to_string())
}
}
```
## Entity Pool Pattern
```rust
struct EntityPool<T> {
available: Vec<Entity<T>>,
in_use: Vec<WeakEntity<T>>,
factory: Box<dyn Fn(&mut App) -> Entity<T>>,
}
impl<T: 'static> EntityPool<T> {
fn new<F>(factory: F) -> Self
where
F: Fn(&mut App) -> Entity<T> + 'static,
{
Self {
available: Vec::new(),
in_use: Vec::new(),
factory: Box::new(factory),
}
}
fn acquire(&mut self, cx: &mut App) -> Entity<T> {
let entity = if let Some(entity) = self.available.pop() {
entity
} else {
(self.factory)(cx)
};
self.in_use.push(entity.downgrade());
entity
}
fn release(&mut self, entity: Entity<T>, cx: &mut App) {
// Reset entity state if needed
entity.update(cx, |state, cx| {
// Reset logic here
cx.notify();
});
self.available.push(entity);
self.cleanup_in_use();
}
fn cleanup_in_use(&mut self) {
self.in_use.retain(|weak| weak.upgrade().is_some());
}
fn pool_size(&self) -> (usize, usize) {
(self.available.len(), self.in_use.len())
}
}
```
These advanced patterns provide powerful abstractions for managing complex entity scenarios while maintaining code quality and performance.

View File

@@ -0,0 +1,382 @@
# Entity API Reference
Complete API documentation for GPUI's entity system.
## Entity Types
### Entity<T>
A strong reference to state of type `T`.
**Methods:**
- `entity_id()``EntityId` - Returns unique identifier
- `downgrade()``WeakEntity<T>` - Creates weak reference
- `read(cx)``&T` - Immutable access to state
- `read_with(cx, |state, cx| ...)``R` - Read with closure, returns closure result
- `update(cx, |state, cx| ...)``R` - Mutable update with `Context<T>`, returns closure result
- `update_in(cx, |state, window, cx| ...)``R` - Update with `Window` access (requires `AsyncWindowContext` or `VisualTestContext`)
**Important Notes:**
- Trying to update an entity while it's already being updated will panic
- Within closures, use the inner `cx` provided to avoid multiple borrow issues
- With async contexts, return values are wrapped in `anyhow::Result`
### WeakEntity<T>
A weak reference to state of type `T`.
**Methods:**
- `upgrade()``Option<Entity<T>>` - Convert to strong reference if still alive
- `read_with(cx, |state, cx| ...)``Result<R>` - Read if entity exists
- `update(cx, |state, cx| ...)``Result<R>` - Update if entity exists
- `update_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.
```rust
let entity = cx.new(|cx| MyState {
count: 0,
name: "Default".to_string(),
});
```
**Parameters:**
- `cx: &mut App` or other context type
- Closure receiving `&mut Context<T>` returning initial state `T`
**Returns:** `Entity<T>`
## Entity Operations
### Reading State
#### read()
Direct read-only access to state.
```rust
let count = my_entity.read(cx).count;
```
**Use when:** Simple field access, no context operations needed.
#### read_with()
Read with context access in closure.
```rust
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>`.
```rust
my_entity.update(cx, |state, cx| {
state.count += 1;
cx.notify(); // Trigger re-render
});
```
**Available Operations:**
- `cx.notify()` - Trigger re-render
- `cx.entity()` - Get current entity
- `cx.emit(event)` - Emit event
- `cx.spawn(task)` - Spawn async task
- Other `Context<T>` methods
#### update_in()
Update with both `Window` and `Context<T>` access.
```rust
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.
```rust
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.
```rust
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.
```rust
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.
```rust
cx.observe_new_entities::<MyState>(|entity, cx| {
println!("New entity created: {:?}", entity.entity_id());
}).detach();
```
## Async Operations
### cx.spawn()
Spawn foreground task (UI thread).
```rust
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).
```rust
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.
```rust
{
let entity = cx.new(|cx| MyState::default());
// entity exists
} // entity dropped here if no other strong references exist
```
**Memory Leak Prevention:**
- Use `WeakEntity` in closures/callbacks
- Use `WeakEntity` for parent-child relationships
- Avoid circular strong references
## EntityId
Every entity has a unique identifier.
```rust
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`:
```rust
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:
```rust
// ❌ 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
```rust
let entity: Entity<T> = cx.new(|cx| T::default());
let weak: WeakEntity<T> = entity.downgrade();
```
### WeakEntity → Entity
```rust
let weak: WeakEntity<T> = entity.downgrade();
let strong: Option<Entity<T>> = weak.upgrade();
```
### AnyEntity
```rust
let any: AnyEntity = entity.into();
let typed: Option<Entity<T>> = any.downcast::<T>();
```
## Best Practice Guidelines
### Always Use Inner cx
```rust
// ✅ 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
```rust
// ✅ 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
```rust
// ✅ 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
});
});
```

View File

@@ -0,0 +1,484 @@
# Entity Best Practices
Guidelines and best practices for effective entity management in GPUI.
## Avoiding Common Pitfalls
### Avoid Entity Borrowing Conflicts
**Problem:** Nested updates can cause borrow checker panics.
```rust
// ❌ Bad: Nested updates can panic
entity1.update(cx, |_, cx| {
entity2.update(cx, |_, cx| {
// This may panic if entities are related
});
});
```
**Solution:** Perform updates sequentially.
```rust
// ✅ Good: Sequential updates
entity1.update(cx, |state1, cx| {
// Update entity1
state1.value = 42;
cx.notify();
});
entity2.update(cx, |state2, cx| {
// Update entity2
state2.value = 100;
cx.notify();
});
```
### Use Weak References in Closures
**Problem:** Strong references in closures can create retain cycles and memory leaks.
```rust
// ❌ Bad: Strong reference creates retain cycle
impl MyComponent {
fn setup_callback(&mut self, cx: &mut Context<Self>) {
let entity = cx.entity(); // Strong reference
some_callback(move || {
entity.update(cx, |state, cx| {
// This closure holds a strong reference
// If the closure itself is retained by the entity, memory leak!
cx.notify();
});
});
}
}
```
**Solution:** Use weak references in closures.
```rust
// ✅ Good: Weak reference prevents retain cycle
impl MyComponent {
fn setup_callback(&mut self, cx: &mut Context<Self>) {
let weak_entity = cx.entity().downgrade(); // Weak reference
some_callback(move || {
// Safe: weak reference doesn't prevent cleanup
let _ = weak_entity.update(cx, |state, cx| {
cx.notify();
});
});
}
}
```
### Use Inner Context in Closures
**Problem:** Using outer context causes multiple borrow errors.
```rust
// ❌ Bad: Using outer cx causes borrow issues
entity.update(cx, |state, inner_cx| {
cx.notify(); // Wrong! Using outer cx
cx.spawn(...); // Multiple borrow error
});
```
**Solution:** Always use the inner context provided to the closure.
```rust
// ✅ Good: Use inner cx
entity.update(cx, |state, inner_cx| {
inner_cx.notify(); // Correct
inner_cx.spawn(...); // Works fine
});
```
### Entity as Props - Use Weak References
**Problem:** Strong entity references in props can create ownership issues.
```rust
// ❌ Questionable: Strong reference in child
struct ChildComponent {
parent: Entity<ParentComponent>, // Strong reference
}
```
**Better:** Use weak references for parent relationships.
```rust
// ✅ Good: Weak reference prevents issues
struct ChildComponent {
parent: WeakEntity<ParentComponent>, // Weak reference
}
impl ChildComponent {
fn notify_parent(&mut self, cx: &mut Context<Self>) {
// Check if parent still exists
if let Ok(_) = self.parent.update(cx, |parent_state, cx| {
// Update parent
cx.notify();
}) {
// Parent successfully updated
}
}
}
```
## Performance Optimization
### Minimize cx.notify() Calls
Each `cx.notify()` triggers a re-render. Batch updates when possible.
```rust
// ❌ Bad: Multiple notifications
impl MyComponent {
fn update_multiple_fields(&mut self, cx: &mut Context<Self>) {
self.field1 = new_value1;
cx.notify(); // Unnecessary intermediate notification
self.field2 = new_value2;
cx.notify(); // Unnecessary intermediate notification
self.field3 = new_value3;
cx.notify();
}
}
```
```rust
// ✅ Good: Single notification after all updates
impl MyComponent {
fn update_multiple_fields(&mut self, cx: &mut Context<Self>) {
self.field1 = new_value1;
self.field2 = new_value2;
self.field3 = new_value3;
cx.notify(); // Single notification
}
}
```
### Conditional Updates
Only notify when state actually changes.
```rust
impl MyComponent {
fn set_value(&mut self, new_value: i32, cx: &mut Context<Self>) {
if self.value != new_value {
self.value = new_value;
cx.notify(); // Only notify if changed
}
}
}
```
### Use read_with for Complex Operations
Prefer `read_with` over separate `read` calls.
```rust
// ❌ Less efficient: Multiple borrows
let state_ref = entity.read(cx);
let value1 = state_ref.field1;
let value2 = state_ref.field2;
// state_ref borrowed for entire scope
// ✅ More efficient: Single borrow with closure
let (value1, value2) = entity.read_with(cx, |state, cx| {
(state.field1, state.field2)
});
```
### Avoid Excessive Entity Creation
Creating entities has overhead. Reuse when appropriate.
```rust
// ❌ Bad: Creating entity per item in render
impl Render for MyList {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div().children(
self.items.iter().map(|item| {
// Don't create entities in render!
let entity = cx.new(|_| item.clone());
ItemView { entity }
})
)
}
}
```
```rust
// ✅ Good: Create entities once, reuse
struct MyList {
item_entities: Vec<Entity<Item>>,
}
impl MyList {
fn add_item(&mut self, item: Item, cx: &mut Context<Self>) {
let entity = cx.new(|_| item);
self.item_entities.push(entity);
cx.notify();
}
}
```
## Entity Lifecycle Management
### Clean Up Weak References
Periodically clean up invalid weak references from collections.
```rust
struct Container {
weak_children: Vec<WeakEntity<Child>>,
}
impl Container {
fn cleanup_invalid_children(&mut self, cx: &mut Context<Self>) {
// Remove weak references that are no longer valid
let before_count = self.weak_children.len();
self.weak_children.retain(|weak| weak.upgrade().is_some());
let after_count = self.weak_children.len();
if before_count != after_count {
cx.notify(); // Notify if list changed
}
}
}
```
### Entity Cloning and Sharing
Understand that cloning `Entity<T>` increases reference count.
```rust
// Each clone increases the reference count
let entity1: Entity<MyState> = cx.new(|_| MyState::default());
let entity2 = entity1.clone(); // Reference count: 2
let entity3 = entity1.clone(); // Reference count: 3
// Entity is dropped only when all references are dropped
drop(entity1); // Reference count: 2
drop(entity2); // Reference count: 1
drop(entity3); // Reference count: 0, entity is deallocated
```
### Proper Resource Cleanup
Implement cleanup in `Drop` or explicit cleanup methods.
```rust
struct ManagedResource {
handle: Option<FileHandle>,
}
impl ManagedResource {
fn close(&mut self, cx: &mut Context<Self>) {
if let Some(handle) = self.handle.take() {
// Explicit cleanup
handle.close();
cx.notify();
}
}
}
impl Drop for ManagedResource {
fn drop(&mut self) {
// Automatic cleanup when entity is dropped
if let Some(handle) = self.handle.take() {
handle.close();
}
}
}
```
## Entity Observation Best Practices
### Detach Subscriptions Appropriately
Call `.detach()` on subscriptions you want to keep alive.
```rust
impl MyComponent {
fn new(other_entity: Entity<OtherComponent>, cx: &mut App) -> Entity<Self> {
cx.new(|cx| {
// Observer will live as long as both entities exist
cx.observe(&other_entity, |this, observed, cx| {
// Handle changes
cx.notify();
}).detach(); // Important: detach to make permanent
Self { /* fields */ }
})
}
}
```
### Avoid Observation Cycles
Don't create mutual observation between entities.
```rust
// ❌ Bad: Mutual observation can cause infinite loops
entity1.update(cx, |_, cx| {
cx.observe(&entity2, |_, _, cx| {
cx.notify(); // May trigger entity2's observer
}).detach();
});
entity2.update(cx, |_, cx| {
cx.observe(&entity1, |_, _, cx| {
cx.notify(); // May trigger entity1's observer → infinite loop
}).detach();
});
```
## Async Best Practices
### Always Use Weak References in Async Tasks
```rust
// ✅ Good: Weak reference in spawned task
impl MyComponent {
fn fetch_data(&mut self, cx: &mut Context<Self>) {
let weak_entity = cx.entity().downgrade();
cx.spawn(async move |cx| {
let data = fetch_from_api().await;
// Entity may have been dropped during fetch
let _ = weak_entity.update(cx, |state, cx| {
state.data = Some(data);
cx.notify();
});
}).detach();
}
}
```
### Handle Async Errors Gracefully
```rust
impl MyComponent {
fn fetch_data(&mut self, cx: &mut Context<Self>) {
let weak_entity = cx.entity().downgrade();
cx.spawn(async move |cx| {
match fetch_from_api().await {
Ok(data) => {
let _ = weak_entity.update(cx, |state, cx| {
state.data = Some(data);
state.error = None;
cx.notify();
});
}
Err(e) => {
let _ = weak_entity.update(cx, |state, cx| {
state.error = Some(e.to_string());
cx.notify();
});
}
}
}).detach();
}
}
```
### Cancellation Patterns
Implement cancellation for long-running tasks.
```rust
struct DataFetcher {
current_task: Option<Task<()>>,
data: Option<String>,
}
impl DataFetcher {
fn fetch_data(&mut self, url: String, cx: &mut Context<Self>) {
// Cancel previous task
self.current_task = None; // Dropping task cancels it
let weak_entity = cx.entity().downgrade();
let task = cx.spawn(async move |cx| {
let data = fetch_from_url(&url).await?;
let _ = weak_entity.update(cx, |state, cx| {
state.data = Some(data);
cx.notify();
});
Ok::<(), anyhow::Error>(())
});
self.current_task = Some(task);
}
}
```
## Testing Best Practices
### Use TestAppContext for Entity Tests
```rust
#[cfg(test)]
mod tests {
use super::*;
use gpui::TestAppContext;
#[gpui::test]
fn test_entity_update(cx: &mut TestAppContext) {
let entity = cx.new(|_| MyState { count: 0 });
entity.update(cx, |state, cx| {
state.count += 1;
assert_eq!(state.count, 1);
});
let count = entity.read(cx).count;
assert_eq!(count, 1);
}
}
```
### Test Entity Observation
```rust
#[gpui::test]
fn test_entity_observation(cx: &mut TestAppContext) {
let observed = cx.new(|_| MyState { value: 0 });
let observer = cx.new(|cx| Observer::new(observed.clone(), cx));
// Update observed entity
observed.update(cx, |state, cx| {
state.value = 42;
cx.notify();
});
// Verify observer was notified
observer.read(cx).assert_observed();
}
```
## Performance Checklist
Before shipping entity-based code, verify:
- [ ] No strong references in closures/callbacks (use `WeakEntity`)
- [ ] No nested entity updates (use sequential updates)
- [ ] Using inner `cx` in update closures
- [ ] Batching updates before calling `cx.notify()`
- [ ] Cleaning up invalid weak references periodically
- [ ] Using `read_with` for complex read operations
- [ ] Properly detaching subscriptions and observers
- [ ] Using weak references in async tasks
- [ ] No observation cycles between entities
- [ ] Proper error handling in async operations
- [ ] Resource cleanup in `Drop` or explicit methods
- [ ] Tests cover entity lifecycle and interactions

View File

@@ -0,0 +1,579 @@
# Entity Patterns
Common patterns and use cases for entity management in GPUI.
## Application Scenarios
### Model-View Separation
Separate business logic (model) from UI (view) using entities.
```rust
struct CounterModel {
count: usize,
listeners: Vec<Box<dyn Fn(usize)>>,
}
struct CounterView {
model: Entity<CounterModel>,
}
impl CounterModel {
fn increment(&mut self, cx: &mut Context<Self>) {
self.count += 1;
// Notify listeners
for listener in &self.listeners {
listener(self.count);
}
cx.notify();
}
fn decrement(&mut self, cx: &mut Context<Self>) {
if self.count > 0 {
self.count -= 1;
cx.notify();
}
}
}
impl CounterView {
fn new(cx: &mut App) -> Entity<Self> {
let model = cx.new(|_cx| CounterModel {
count: 0,
listeners: Vec::new(),
});
cx.new(|cx| Self { model })
}
fn increment_count(&mut self, cx: &mut Context<Self>) {
self.model.update(cx, |model, cx| {
model.increment(cx);
});
}
}
impl Render for CounterView {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let count = self.model.read(cx).count;
div()
.child(format!("Count: {}", count))
.child(
Button::new("increment")
.label("Increment")
.on_click(cx.listener(|this, _, cx| {
this.increment_count(cx);
}))
)
}
}
```
### Component State Management
Managing complex component state with entities.
```rust
struct TodoList {
todos: Vec<Todo>,
filter: TodoFilter,
next_id: usize,
}
struct Todo {
id: usize,
text: String,
completed: bool,
}
enum TodoFilter {
All,
Active,
Completed,
}
impl TodoList {
fn new() -> Self {
Self {
todos: Vec::new(),
filter: TodoFilter::All,
next_id: 0,
}
}
fn add_todo(&mut self, text: String, cx: &mut Context<Self>) {
self.todos.push(Todo {
id: self.next_id,
text,
completed: false,
});
self.next_id += 1;
cx.notify();
}
fn toggle_todo(&mut self, id: usize, cx: &mut Context<Self>) {
if let Some(todo) = self.todos.iter_mut().find(|t| t.id == id) {
todo.completed = !todo.completed;
cx.notify();
}
}
fn remove_todo(&mut self, id: usize, cx: &mut Context<Self>) {
self.todos.retain(|t| t.id != id);
cx.notify();
}
fn set_filter(&mut self, filter: TodoFilter, cx: &mut Context<Self>) {
self.filter = filter;
cx.notify();
}
fn visible_todos(&self) -> impl Iterator<Item = &Todo> {
self.todos.iter().filter(move |todo| match self.filter {
TodoFilter::All => true,
TodoFilter::Active => !todo.completed,
TodoFilter::Completed => todo.completed,
})
}
}
```
### Cross-Entity Communication
Coordinating state between parent and child entities.
```rust
struct ParentComponent {
child_entities: Vec<Entity<ChildComponent>>,
global_message: String,
}
struct ChildComponent {
id: usize,
message: String,
parent: WeakEntity<ParentComponent>,
}
impl ParentComponent {
fn new(cx: &mut App) -> Entity<Self> {
cx.new(|cx| Self {
child_entities: Vec::new(),
global_message: String::new(),
})
}
fn add_child(&mut self, cx: &mut Context<Self>) {
let parent_weak = cx.entity().downgrade();
let child_id = self.child_entities.len();
let child = cx.new(|cx| ChildComponent {
id: child_id,
message: String::new(),
parent: parent_weak,
});
self.child_entities.push(child);
cx.notify();
}
fn broadcast_message(&mut self, message: String, cx: &mut Context<Self>) {
self.global_message = message.clone();
// Update all children
for child in &self.child_entities {
child.update(cx, |child_state, cx| {
child_state.message = message.clone();
cx.notify();
});
}
cx.notify();
}
}
impl ChildComponent {
fn notify_parent(&mut self, message: String, cx: &mut Context<Self>) {
if let Ok(_) = self.parent.update(cx, |parent_state, cx| {
parent_state.global_message = format!("Child {}: {}", self.id, message);
cx.notify();
}) {
// Parent successfully notified
}
}
}
```
### Async Operations with Entities
Managing async state updates.
```rust
struct DataLoader {
loading: bool,
data: Option<String>,
error: Option<String>,
}
impl DataLoader {
fn new() -> Self {
Self {
loading: false,
data: None,
error: None,
}
}
fn load_data(&mut self, cx: &mut Context<Self>) {
// Set loading state
self.loading = true;
self.error = None;
cx.notify();
// Get weak reference for async task
let entity = cx.entity().downgrade();
cx.spawn(async move |cx| {
// Simulate async operation
tokio::time::sleep(Duration::from_secs(2)).await;
let result = fetch_data().await;
// Update entity with result
let _ = entity.update(cx, |state, cx| {
state.loading = false;
match result {
Ok(data) => state.data = Some(data),
Err(e) => state.error = Some(e.to_string()),
}
cx.notify();
});
}).detach();
}
}
async fn fetch_data() -> Result<String, anyhow::Error> {
// Actual fetch implementation
Ok("Fetched data".to_string())
}
```
### Background Task Coordination
Using background tasks with entity updates.
```rust
struct ImageProcessor {
images: Vec<ProcessedImage>,
processing: bool,
}
struct ProcessedImage {
path: PathBuf,
thumbnail: Option<Vec<u8>>,
}
impl ImageProcessor {
fn process_images(&mut self, paths: Vec<PathBuf>, cx: &mut Context<Self>) {
self.processing = true;
cx.notify();
let entity = cx.entity().downgrade();
cx.background_spawn({
let paths = paths.clone();
async move {
let mut processed = Vec::new();
for path in paths {
// Process image on background thread
let thumbnail = generate_thumbnail(&path).await;
processed.push((path, thumbnail));
}
// Send results back to foreground
processed
}
})
.then(cx.spawn(move |processed, cx| {
// Update entity on foreground thread
let _ = entity.update(cx, |state, cx| {
for (path, thumbnail) in processed {
state.images.push(ProcessedImage {
path,
thumbnail: Some(thumbnail),
});
}
state.processing = false;
cx.notify();
});
}))
.detach();
}
}
```
## Common Patterns
### 1. Stateful Components
Use entities for components that maintain internal state.
```rust
struct StatefulComponent {
value: i32,
history: Vec<i32>,
}
impl StatefulComponent {
fn update_value(&mut self, new_value: i32, cx: &mut Context<Self>) {
self.history.push(self.value);
self.value = new_value;
cx.notify();
}
fn undo(&mut self, cx: &mut Context<Self>) {
if let Some(prev_value) = self.history.pop() {
self.value = prev_value;
cx.notify();
}
}
}
```
### 2. Shared State
Share state between multiple components using entities.
```rust
struct SharedState {
theme: Theme,
user: Option<User>,
}
struct ComponentA {
shared: Entity<SharedState>,
}
struct ComponentB {
shared: Entity<SharedState>,
}
// Both components can read/update the same shared state
impl ComponentA {
fn update_theme(&mut self, theme: Theme, cx: &mut Context<Self>) {
self.shared.update(cx, |state, cx| {
state.theme = theme;
cx.notify();
});
}
}
```
### 3. Event Coordination
Use entities to coordinate events between components.
```rust
struct EventCoordinator {
listeners: Vec<WeakEntity<dyn EventListener>>,
}
trait EventListener {
fn on_event(&mut self, event: &AppEvent, cx: &mut App);
}
impl EventCoordinator {
fn emit_event(&mut self, event: AppEvent, cx: &mut Context<Self>) {
// Notify all listeners
self.listeners.retain(|weak_listener| {
weak_listener.update(cx, |listener, cx| {
listener.on_event(&event, cx);
}).is_ok()
});
cx.notify();
}
}
```
### 4. Async State Management
Manage state that changes based on async operations.
```rust
struct AsyncState<T> {
state: AsyncValue<T>,
}
enum AsyncValue<T> {
Loading,
Loaded(T),
Error(String),
}
impl<T> AsyncState<T> {
fn is_loading(&self) -> bool {
matches!(self.state, AsyncValue::Loading)
}
fn value(&self) -> Option<&T> {
match &self.state {
AsyncValue::Loaded(v) => Some(v),
_ => None,
}
}
}
```
### 5. Parent-Child Relationships
Manage hierarchical relationships with weak references.
```rust
struct Parent {
children: Vec<Entity<Child>>,
}
struct Child {
parent: WeakEntity<Parent>,
data: String,
}
impl Child {
fn notify_parent_of_change(&mut self, cx: &mut Context<Self>) {
if let Ok(_) = self.parent.update(cx, |parent, cx| {
// Parent can react to child change
cx.notify();
}) {
// Successfully notified
}
}
}
```
### 6. Observer Pattern
React to entity state changes using observers.
```rust
struct Observable {
value: i32,
}
struct Observer {
observed: Entity<Observable>,
}
impl Observer {
fn new(observed: Entity<Observable>, cx: &mut App) -> Entity<Self> {
cx.new(|cx| {
// Observe the entity
cx.observe(&observed, |this, observed_entity, cx| {
// React to changes
let value = observed_entity.read(cx).value;
println!("Value changed to: {}", value);
}).detach();
Self { observed }
})
}
}
```
### 7. Event Subscription
Handle events emitted by other entities.
```rust
#[derive(Clone)]
enum DataEvent {
Updated,
Deleted,
}
struct DataSource {
data: Vec<String>,
}
impl DataSource {
fn update_data(&mut self, cx: &mut Context<Self>) {
// Update data
cx.emit(DataEvent::Updated);
cx.notify();
}
}
struct DataConsumer {
source: Entity<DataSource>,
}
impl DataConsumer {
fn new(source: Entity<DataSource>, cx: &mut App) -> Entity<Self> {
cx.new(|cx| {
// Subscribe to events
cx.subscribe(&source, |this, source, event: &DataEvent, cx| {
match event {
DataEvent::Updated => {
// Handle update
cx.notify();
}
DataEvent::Deleted => {
// Handle deletion
}
}
}).detach();
Self { source }
})
}
}
```
### 8. Resource Management
Manage external resources with proper cleanup.
```rust
struct FileHandle {
path: PathBuf,
file: Option<File>,
}
impl FileHandle {
fn open(&mut self, cx: &mut Context<Self>) -> Result<()> {
self.file = Some(File::open(&self.path)?);
cx.notify();
Ok(())
}
fn close(&mut self, cx: &mut Context<Self>) {
self.file = None;
cx.notify();
}
}
impl Drop for FileHandle {
fn drop(&mut self) {
// Cleanup when entity is dropped
if let Some(file) = self.file.take() {
drop(file);
}
}
}
```
## Pattern Selection Guide
| Need | Pattern | Complexity |
|------|---------|------------|
| Component with internal state | Stateful Components | Low |
| State shared by multiple components | Shared State | Low |
| Coordinate events between components | Event Coordination | Medium |
| Handle async data fetching | Async State Management | Medium |
| Parent-child component hierarchy | Parent-Child Relationships | Medium |
| React to state changes | Observer Pattern | Medium |
| Handle custom events | Event Subscription | Medium-High |
| Manage external resources | Resource Management | High |
Choose the simplest pattern that meets your requirements. Combine patterns as needed for complex scenarios.