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.
|
||||
Reference in New Issue
Block a user