add gpui skills
This commit is contained in:
168
.agents/skills/gpui-entity/SKILL.md
Normal file
168
.agents/skills/gpui-entity/SKILL.md
Normal 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
|
||||
528
.agents/skills/gpui-entity/references/advanced.md
Normal file
528
.agents/skills/gpui-entity/references/advanced.md
Normal 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.
|
||||
382
.agents/skills/gpui-entity/references/api-reference.md
Normal file
382
.agents/skills/gpui-entity/references/api-reference.md
Normal 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
|
||||
});
|
||||
});
|
||||
```
|
||||
484
.agents/skills/gpui-entity/references/best-practices.md
Normal file
484
.agents/skills/gpui-entity/references/best-practices.md
Normal 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
|
||||
579
.agents/skills/gpui-entity/references/patterns.md
Normal file
579
.agents/skills/gpui-entity/references/patterns.md
Normal 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.
|
||||
Reference in New Issue
Block a user