Reviewed-on: #18 Co-authored-by: Ren Amamiya <reya@lume.nu> Co-committed-by: Ren Amamiya <reya@lume.nu>
529 lines
12 KiB
Markdown
529 lines
12 KiB
Markdown
# 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.
|