feat: refactor to use gpui event instead of local state (#18)
Reviewed-on: #18 Co-authored-by: Ren Amamiya <reya@lume.nu> Co-committed-by: Ren Amamiya <reya@lume.nu>
This commit was merged in pull request #18.
This commit is contained in:
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