Reviewed-on: #18 Co-authored-by: Ren Amamiya <reya@lume.nu> Co-committed-by: Ren Amamiya <reya@lume.nu>
12 KiB
12 KiB
Advanced Entity Patterns
Advanced techniques for sophisticated entity management scenarios.
Entity Collections Management
Dynamic Collection with Cleanup
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
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
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
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
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
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
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
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
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
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.