Files
coop/.agents/skills/gpui-entity/references/api-reference.md
2026-03-08 08:41:46 +07:00

8.2 KiB

Entity API Reference

Complete API documentation for GPUI's entity system.

Entity Types

Entity

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

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.

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.

let count = my_entity.read(cx).count;

Use when: Simple field access, no context operations needed.

read_with()

Read with context access in closure.

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

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.

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.

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.

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.

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.

cx.observe_new_entities::<MyState>(|entity, cx| {
    println!("New entity created: {:?}", entity.entity_id());
}).detach();

Async Operations

cx.spawn()

Spawn foreground task (UI thread).

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

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.

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

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:

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:

// ❌ 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

let entity: Entity<T> = cx.new(|cx| T::default());
let weak: WeakEntity<T> = entity.downgrade();

WeakEntity → Entity

let weak: WeakEntity<T> = entity.downgrade();
let strong: Option<Entity<T>> = weak.upgrade();

AnyEntity

let any: AnyEntity = entity.into();
let typed: Option<Entity<T>> = any.downcast::<T>();

Best Practice Guidelines

Always Use Inner cx

// ✅ 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

// ✅ 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

// ✅ 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
    });
});