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,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.