From 86ddc7d383a5a062985ca2729946477777b66ed2 Mon Sep 17 00:00:00 2001 From: John McCardle Date: Sat, 29 Nov 2025 23:28:38 +0000 Subject: [PATCH] Delete page "Grid-Entity-Lifecycle.-" --- Grid-Entity-Lifecycle.-.md | 632 ------------------------------------- 1 file changed, 632 deletions(-) delete mode 100644 Grid-Entity-Lifecycle.-.md diff --git a/Grid-Entity-Lifecycle.-.md b/Grid-Entity-Lifecycle.-.md deleted file mode 100644 index 15e5bec..0000000 --- a/Grid-Entity-Lifecycle.-.md +++ /dev/null @@ -1,632 +0,0 @@ -# Grid-Entity Lifecycle - -## Overview - -Entities in McRogueFace have a lifecycle tied to the grids they inhabit. Understanding this lifecycle is critical for proper memory management, collision detection, and rendering. - -**Parent Page:** [[Grid-System]] - -**Related Pages:** -- [[Entity-Management]] - Entity creation and properties -- [[Grid-Rendering-Pipeline]] - How entities are rendered within grids -- [[UI-Component-Hierarchy]] - Entity as UIDrawable - -**Key Files:** -- `src/UIEntity.h` - Entity class definition -- `src/UIEntity.cpp` - Entity implementation -- `src/UIGrid.h` - Grid's entity collection - -**Related Issues:** -- [#30](../../issues/30) - Entity.die() cleanup (completed) -- [#115](../../issues/115) - SpatialHash for efficient entity queries -- [#117](../../issues/117) - Memory pool for entities - ---- - -## Entity Lifecycle States - -An entity progresses through these states: - -``` -1. Created (entity.grid == None) - ↓ -2. Added to Grid (entity.grid = grid) - ↓ -3. Active (updates, renders, collides) - ↓ -4. Removed from Grid (entity.grid = None) - ↓ -5. Python GC Cleanup (shared_ptr refcount → 0) -``` - ---- - -## State 1: Entity Creation - -### Creating an Entity - -Entities are created without a grid: - -```python -import mcrfpy - -# Create entity with position and sprite -player = mcrfpy.Entity( - grid_pos=(10, 15), # Initial position - sprite_index=1 # Sprite from texture -) - -# At this point: -# - player.grid is None -# - Entity exists in Python but is not rendered -# - Entity has no collision detection -``` - -### Entity Constructor Parameters - -```python -# Full constructor -entity = mcrfpy.Entity( - grid_pos=(x, y), # Grid coordinates (int, int) - sprite_index=5, # Sprite index from texture sheet - size=(16, 16) # Optional: sprite size override -) -``` - -**Default values:** -- `sprite_index`: 0 -- `size`: Inherited from texture or (16, 16) - ---- - -## State 2: Adding to Grid - -### The Grid Property - -Entities track which grid they belong to via the `entity.grid` property: - -```python -# Check if entity is on a grid -if player.grid is None: - print("Player is not on any grid") -else: - print(f"Player is on grid: {player.grid}") -``` - -### Adding Entity to Grid - -```python -# Create grid and entity -grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600)) -player = mcrfpy.Entity(grid_pos=(25, 25), sprite_index=1) - -# Add entity to grid -grid.entities.append(player) - -# Now: -# - player.grid == grid (automatically set) -# - Entity will render with grid -# - Entity participates in collision detection -``` - -**Key Insight:** `grid.entities.append()` automatically sets `entity.grid` to point back to the grid. - -### Multiple Entities - -```python -# Add many entities -enemies = [] -for i in range(10): - enemy = mcrfpy.Entity( - grid_pos=(random.randint(0, 49), random.randint(0, 49)), - sprite_index=random.randint(10, 15) - ) - grid.entities.append(enemy) - enemies.append(enemy) - -# All enemies now have enemy.grid == grid -``` - ---- - -## State 3: Active Entity - -### What Happens When Entity is on Grid - -Once added to a grid, the entity: - -1. **Renders** during grid render pass (Stage 3 of [[Grid-Rendering-Pipeline]]) -2. **Updates** position and velocity every frame -3. **Collides** with grid cells and other entities (if collision enabled) -4. **Can be queried** via grid spatial queries - -### Entity Position - -Entities have **floating-point positions** allowing smooth sub-cell movement: - -```python -# Fractional positions -player.x = 10.5 # Halfway between cells 10 and 11 -player.y = 15.75 - -# Position is independent of grid coordinates -# grid_pos property rounds to nearest cell -print(player.grid_pos) # (11, 16) - rounded for convenience -``` - -### Entity Velocity - -Entities have built-in velocity: - -```python -# Set velocity -player.velocity_x = 0.1 # Move right 0.1 cells per frame -player.velocity_y = 0.0 - -# Velocity is applied automatically during render -# (position updated by engine, not Python) -``` - -### Collision with Grid - -Entities can detect grid cell properties: - -```python -# Check if entity is on walkable cell -cell = grid.at((int(player.x), int(player.y))) -if not cell.walkable: - print("Player is on non-walkable terrain!") - # Push player back or handle collision -``` - -### Collision with Other Entities - -Check for entity collisions: - -```python -def check_collision(entity1, entity2, radius=0.5): - """Check if two entities are within radius of each other""" - dx = entity1.x - entity2.x - dy = entity1.y - entity2.y - distance = (dx*dx + dy*dy) ** 0.5 - return distance < radius - -# Check player collision with all enemies -for enemy in enemies: - if check_collision(player, enemy): - print("Player hit enemy!") - handle_combat(player, enemy) -``` - -**Performance Note:** O(n²) collision detection. See **#115** for spatial hash optimization. - ---- - -## State 4: Removing from Grid - -### Method 1: Remove via Collection - -```python -# Remove entity from grid -grid.entities.remove(player) - -# Now: -# - player.grid is None (automatically cleared) -# - Entity no longer renders -# - Entity no longer participates in collision -# - Python object still exists (can re-add later) -``` - -### Method 2: Clear Grid Property - -```python -# Alternative: clear grid property directly -player.grid = None - -# Entity is removed from grid's entity collection -# (This happens automatically when grid property is cleared) -``` - -### Entity.die() Method - -**Issue #30** added `Entity.die()` for safe cleanup: - -```python -# Proper entity cleanup -player.die() - -# This: -# 1. Removes entity from grid (if on one) -# 2. Clears entity.grid property -# 3. Marks entity as "dead" (dead=True) -# 4. Stops all animations on entity -``` - -**Best Practice:** Always use `entity.die()` instead of manual removal. - -### Dead Entities - -After `die()` is called: - -```python -if entity.dead: - print("Entity is dead, don't update") - -# Dead entities should not be updated or interacted with -# Remove dead entities from your tracking lists -enemies = [e for e in enemies if not e.dead] -``` - ---- - -## State 5: Python GC Cleanup - -### Shared Pointer Lifecycle - -Entities use `shared_ptr` in C++: - -```python -# Create entity -entity = mcrfpy.Entity(grid_pos=(10, 10), sprite_index=1) -# C++ shared_ptr refcount: 1 (Python owns it) - -grid.entities.append(entity) -# C++ shared_ptr refcount: 2 (Python + grid's list) - -entity = None # Drop Python reference -# C++ shared_ptr refcount: 1 (grid still owns it) - -grid.entities.remove(entity) -# C++ shared_ptr refcount: 0 → C++ object deleted -``` - -### Memory Leaks to Avoid - -**Leak Pattern 1: Circular References** - -```python -# BAD: Entity stores reference to itself -entity.self_ref = entity - -# Even after removing from grid, refcount never reaches 0 -grid.entities.remove(entity) -# Entity leaks! - -# FIX: Don't store circular references -``` - -**Leak Pattern 2: Forgotten Entity Lists** - -```python -# BAD: Keep entity in tracking list after death -enemies = [enemy1, enemy2, enemy3] -enemy1.die() - -# enemy1 removed from grid, but enemies list still holds reference -# Entity leaks! - -# FIX: Clean up tracking lists -enemies = [e for e in enemies if not e.dead] -``` - -### Weak References - -For observer patterns, use weak references: - -```python -# Can't use weakref directly on mcrfpy.Entity currently -# Workaround: Use entity IDs - -entity_id = id(entity) -entity_map = {entity_id: entity} - -# Later, check if entity still exists -if entity_id in entity_map and not entity_map[entity_id].dead: - entity_map[entity_id].update() -``` - ---- - -## Common Patterns - -### Spawning Enemies - -```python -def spawn_enemy(grid, x, y, sprite_index): - """Spawn enemy at grid position""" - enemy = mcrfpy.Entity(grid_pos=(x, y), sprite_index=sprite_index) - grid.entities.append(enemy) - - # Set AI behavior - enemy.velocity_x = random.uniform(-0.1, 0.1) - enemy.velocity_y = random.uniform(-0.1, 0.1) - - # Track enemy for updates - enemies.append(enemy) - - return enemy - -# Spawn 10 enemies -for i in range(10): - spawn_enemy(grid, random.randint(0, 49), random.randint(0, 49), - sprite_index=random.randint(10, 15)) -``` - -### Entity Death and Cleanup - -```python -def kill_entity(entity): - """Kill entity and play death animation""" - if entity.dead: - return # Already dead - - # Play death animation - mcrfpy.animate(entity, "opacity", 0, duration=500, - easing="ease_out_cubic") - - # Mark as dead - entity.die() - - # Schedule cleanup after animation - def cleanup(ms): - # Remove from tracking - if entity in enemies: - enemies.remove(entity) - - mcrfpy.setTimer(f"cleanup_{id(entity)}", cleanup, 500) - -# Kill all enemies -for enemy in enemies[:]: # Copy list to avoid modification during iteration - kill_entity(enemy) -``` - -### Moving Between Grids - -```python -def move_to_grid(entity, from_grid, to_grid, new_x, new_y): - """Move entity from one grid to another""" - # Remove from old grid - if entity.grid == from_grid: - from_grid.entities.remove(entity) - - # Update position - entity.x = new_x - entity.y = new_y - - # Add to new grid - to_grid.entities.append(entity) - - # entity.grid now points to to_grid - -# Example: player enters new room -move_to_grid(player, current_room.grid, next_room.grid, 5, 5) -``` - -### Entity Pooling (Future Optimization) - -**Issue #117** proposes entity memory pooling: - -```python -# Proposed pattern (not yet implemented) -class EntityPool: - def __init__(self, grid, sprite_index, pool_size=100): - self.pool = [] - self.grid = grid - self.sprite_index = sprite_index - - # Pre-allocate entities - for _ in range(pool_size): - e = mcrfpy.Entity(grid_pos=(0, 0), sprite_index=sprite_index) - e.active = False - self.pool.append(e) - - def spawn(self, x, y): - """Get entity from pool""" - for entity in self.pool: - if not entity.active: - entity.x = x - entity.y = y - entity.active = True - self.grid.entities.append(entity) - return entity - return None # Pool exhausted - - def despawn(self, entity): - """Return entity to pool""" - entity.active = False - self.grid.entities.remove(entity) - -# Use pool -enemy_pool = EntityPool(grid, sprite_index=10, pool_size=50) -enemy = enemy_pool.spawn(20, 20) -# ... later ... -enemy_pool.despawn(enemy) -``` - ---- - -## Performance Considerations - -### Entity Collection Iteration - -Grid entity collection is a `std::list>`: - -```python -# Iteration is O(n) -for entity in grid.entities: - entity.update() - -# Removal during iteration is safe (list allows it) -for entity in grid.entities: - if entity.health <= 0: - entity.die() # Safe to remove during iteration -``` - -**Performance:** -- Iteration: O(n) -- Append: O(1) -- Remove: O(n) - must search list -- Count: O(n) - must traverse list - -### Spatial Queries (Current: O(n)) - -```python -# Find entities near position - O(n) -def find_entities_near(grid, x, y, radius): - nearby = [] - for entity in grid.entities: - dx = entity.x - x - dy = entity.y - y - if dx*dx + dy*dy <= radius*radius: - nearby.append(entity) - return nearby - -# Very slow for 1000+ entities! -``` - -**Future Optimization:** Issue #115 proposes SpatialHash for O(1) spatial queries. - -### Entity Update Patterns - -**Pattern 1: Update All Entities** - -```python -# Update every entity every frame - expensive! -def update_all_entities(ms): - for entity in grid.entities: - entity.ai_update() - entity.check_collision() - -mcrfpy.setTimer("entity_update", update_all_entities, 16) # 60 FPS -``` - -**Pattern 2: Update Only Active Entities** - -```python -# Track only active entities -active_entities = [] - -def update_active_entities(ms): - for entity in active_entities: - entity.ai_update() - - # Remove dead entities - active_entities[:] = [e for e in active_entities if not e.dead] - -mcrfpy.setTimer("entity_update", update_active_entities, 16) -``` - -**Pattern 3: Update Based on Distance** - -```python -# Only update entities near player -def update_nearby_entities(ms): - for entity in grid.entities: - distance = abs(entity.x - player.x) + abs(entity.y - player.y) - if distance < 20: # Within 20 cells - entity.ai_update() - -mcrfpy.setTimer("entity_update", update_nearby_entities, 16) -``` - ---- - -## Troubleshooting - -### Issue: Entity Doesn't Render - -**Causes:** -1. Entity not added to grid -2. Entity position out of viewport bounds -3. Entity opacity = 0 -4. Grid not added to scene UI - -**Debug:** -```python -if entity.grid is None: - print("Entity not on grid!") -else: - print(f"Entity on grid at ({entity.x}, {entity.y})") - print(f"Entity opacity: {entity.opacity}") - print(f"Grid in scene UI: {grid in mcrfpy.sceneUI('game')}") -``` - -### Issue: Entity Collision Not Working - -**Cause:** Forgot to check `entity.grid` before collision test. - -**Fix:** -```python -def check_collision(e1, e2): - # Ensure both entities are on same grid - if e1.grid != e2.grid: - return False - - # ... collision logic ... -``` - -### Issue: Entity Memory Leak - -**Cause:** Entity still referenced in Python list after death. - -**Debug:** -```python -import sys - -# Check refcount -print(f"Entity refcount: {sys.getrefcount(entity)}") -# Should be 2 (Python + grid) if only on grid - -entity.die() -print(f"After die: {sys.getrefcount(entity)}") -# Should be 1 (just Python variable) - -# Find references -import gc -print(gc.get_referrers(entity)) -``` - -### Issue: Entities Flicker When Moving - -**Cause:** Position updated during render, causing frame-to-frame inconsistency. - -**Fix:** Update positions in timer, not during render: -```python -def update_positions(ms): - for entity in entities: - entity.x += entity.velocity_x - entity.y += entity.velocity_y - -mcrfpy.setTimer("position_update", update_positions, 16) -``` - ---- - -## API Reference - -See [`docs/api_reference_dynamic.html`](../../src/branch/master/docs/api_reference_dynamic.html) for complete Entity API. - -**Entity Constructor:** -- `mcrfpy.Entity(grid_pos=(x, y), sprite_index=i, size=(w, h))` → Entity - -**Entity Properties:** -- `entity.grid` - Grid entity belongs to (None if not on grid) -- `entity.x`, `entity.y` - Floating-point position -- `entity.velocity_x`, `entity.velocity_y` - Velocity (cells per frame) -- `entity.dead` - Boolean, True after die() called - -**Entity Methods:** -- `entity.die()` - Remove from grid and mark as dead - -**Grid Entity Collection:** -- `grid.entities` - List-like collection of entities -- `grid.entities.append(entity)` - Add entity to grid -- `grid.entities.remove(entity)` - Remove entity from grid - ---- - -**Navigation:** -- [[Grid-System]] - Parent page -- [[Entity-Management]] - Entity creation and properties -- [[Grid-Rendering-Pipeline]] - Entity rendering within grids -- [[AI-and-Pathfinding]] - Entity AI and movement