Add "Proposal-Next-Gen-Grid-Entity-System"

John McCardle 2025-10-25 22:52:39 +00:00
parent a0eb64142c
commit 77a8c011e4
1 changed files with 436 additions and 0 deletions

@ -0,0 +1,436 @@
# Proposal: Next-Generation Grid & Entity System
**Status:** Design Phase
**Complexity:** Major architectural overhaul
**Impact:** Grid System, Entity Management, Performance
**Related Pages:**
- [[Grid-System]] - Current grid architecture
- [[Entity-Management]] - Current entity usage
- [[Grid-Rendering-Pipeline]] - Current rendering architecture
**Source Documents:**
- `NEXT_GEN_GRIDS_ENTITIES_SHORTCOMINGS.md` - Analysis of current limitations
- `NEXT_GEN_GRIDS_ENTITIES_PROPOSAL.md` - Detailed technical proposal
- `NEXT_GEN_GRIDS_ENTITIES_IDEATION.md` - Use cases and ideation
**Related Issues:**
- [#115](../../issues/115) - SpatialHash for 10,000+ entities
- [#116](../../issues/116) - Dirty flag system
- [#113](../../issues/113) - Batch operations
- [#117](../../issues/117) - Memory pool
- [#123](../../issues/123) - Subgrid system
- [#124](../../issues/124) - Grid Point Animation
---
## Executive Summary
The current UIEntity/UIGrid system has fundamental architectural limitations preventing implementation of modern roguelike features. This proposal outlines a comprehensive redesign supporting:
- **Flexible entity content** - Entities containing any UIDrawable (Frame, Caption, Grid, Sprite)
- **Multi-tile entities** - 2x2, 3x3, or arbitrary-sized creatures and structures
- **Custom layer system** - Weather effects, particle layers, UI overlays
- **Spatial optimization** - O(1) entity queries via spatial hashing
- **Memory efficiency** - Optional gridstate, chunk-based loading
**Key Insight:** Maintain entity as grid-specific *container* (no inheritance from UIDrawable), but allow flexible *content* (any UIDrawable).
---
## Current Limitations
### 1. Entity Type Rigidity
**Problem:**
- UIEntity hardcoded to contain only UISprite
- Cannot place Frames, Captions, or Grids on grids
- Blocks speech bubbles, nested grids, complex UI
**Current Code:**
```cpp
class UIEntity {
UISprite sprite; // Hardcoded!
// Should be: std::shared_ptr<UIDrawable> content;
}
```
### 2. Single-Tile Limitation
**Problem:**
- Entity position is single point
- No concept of dimensions or occupied tiles
- Blocks large enemies (2x2 dragons), multi-tile structures (castle doors)
**Missing:**
- `width`/`height` properties
- Spatial occupancy tracking
- Collision detection for multi-tile entities
### 3. Fixed Layer System
**Problem:**
- Grid has three hardcoded layers: tiles, entities, visibility
- No custom layers
- Blocks cloud layer, particle effects, weather overlays
### 4. Performance Issues
**Problem:**
- Linear O(n) iteration through all entities
- No spatial indexing
- Full grid re-render every frame
**Current Code:**
```cpp
// O(n) iteration every frame
for (auto e : *entities) {
if (in_viewport(e)) render(e);
}
```
### 5. Memory Inefficiency
**Problem:**
- Every entity maintains full gridstate vector (width × height)
- Decorative entities (clouds) waste memory on visibility data
- Cannot unload distant chunks
---
## Proposed Architecture
### Core Change 1: Flexible Entity Content
```cpp
class UIEntity { // No inheritance - grid-specific container
private:
std::shared_ptr<UIDrawable> content; // Any drawable!
sf::Vector2f gridPosition; // Position in grid coords
sf::Vector2i dimensions; // Size in tiles (default 1x1)
std::set<sf::Vector2i> occupiedTiles; // Cached occupied positions
std::vector<UIGridPointState> gridstate; // Optional perspective data
public:
void setContent(std::shared_ptr<UIDrawable> drawable);
void renderAt(sf::RenderTarget& target, sf::Vector2f pixelPos);
bool occupies(int x, int y) const;
};
```
**Python API:**
```python
# Entity with sprite (backward compatible)
enemy = mcrfpy.Entity(grid_pos=(10, 10), sprite_index=5)
# Entity with frame (NEW - speech bubble)
speech_frame = mcrfpy.Frame(size=(100, 50))
speech_caption = mcrfpy.Caption(text="Hello!")
speech_frame.append(speech_caption)
speech_entity = mcrfpy.Entity(grid_pos=(player.x, player.y - 2))
speech_entity.content = speech_frame
# Entity with nested grid (NEW - mini-map)
minimap_grid = mcrfpy.Grid(grid_size=(20, 20), pos=(0, 0), size=(100, 100))
minimap_entity = mcrfpy.Entity(grid_pos=(5, 5))
minimap_entity.content = minimap_grid
```
### Core Change 2: Multi-Tile Entities
```cpp
class GridOccupancyMap {
private:
std::unordered_map<int, std::set<std::shared_ptr<UIEntity>>> spatialHash;
int cellSize = 16;
public:
void addEntity(std::shared_ptr<UIEntity> entity);
void removeEntity(std::shared_ptr<UIEntity> entity);
std::vector<std::shared_ptr<UIEntity>> getEntitiesAt(int x, int y); // O(1)
std::vector<std::shared_ptr<UIEntity>> getEntitiesInRect(sf::IntRect rect);
};
```
**Python API:**
```python
# Large enemy (2x2 tiles)
dragon = mcrfpy.Entity(
grid_pos=(20, 20),
sprite_index=10,
dimensions=(2, 2) # NEW: multi-tile support
)
# Check what tiles dragon occupies
occupied = dragon.occupied_tiles # [(20, 20), (21, 20), (20, 21), (21, 21)]
# Collision detection accounts for size
if grid.can_move_to(dragon, new_x, new_y):
dragon.x = new_x
dragon.y = new_y
```
### Core Change 3: Flexible Layer System
```cpp
class GridLayer {
public:
enum class Type { TILE, ENTITY, EFFECT, OVERLAY, CUSTOM };
private:
Type type;
std::string name;
int zOrder;
float opacity = 1.0f;
bool visible = true;
std::shared_ptr<EntityCollection> entities; // For ENTITY layers
public:
virtual void render(UIGrid* grid, sf::RenderTarget& target);
};
class UIGrid : public UIDrawable {
private:
std::map<std::string, std::shared_ptr<GridLayer>> layers;
std::vector<std::shared_ptr<GridLayer>> sortedLayers; // By zOrder
public:
std::shared_ptr<GridLayer> addLayer(const std::string& name,
GridLayer::Type type, int zOrder);
};
```
**Python API:**
```python
# Create custom layers
grid.add_layer("clouds", mcrfpy.LayerType.EFFECT, z_order=100)
grid.add_layer("particles", mcrfpy.LayerType.EFFECT, z_order=50)
grid.add_layer("units", mcrfpy.LayerType.ENTITY, z_order=10)
# Add entities to specific layers
cloud = mcrfpy.Entity(grid_pos=(15, 15), sprite_index=20)
grid.get_layer("clouds").entities.append(cloud)
# Layer control
grid.get_layer("clouds").opacity = 0.5
grid.get_layer("clouds").visible = False
```
### Core Change 4: Spatial Optimization
**SpatialHash Implementation:**
```cpp
int hashPosition(int x, int y) const {
return (x / cellSize) * 1000000 + (y / cellSize);
}
std::vector<std::shared_ptr<UIEntity>> getEntitiesInRect(sf::IntRect rect) {
std::set<std::shared_ptr<UIEntity>> result;
// Only check relevant hash cells
for (int y = rect.top; y < rect.top + rect.height; y += cellSize) {
for (int x = rect.left; x < rect.left + rect.width; x += cellSize) {
int hash = hashPosition(x, y);
if (spatialHash.count(hash)) {
result.insert(spatialHash[hash].begin(), spatialHash[hash].end());
}
}
}
return std::vector<std::shared_ptr<UIEntity>>(result.begin(), result.end());
}
```
**Performance Impact:**
- Current: O(n) for entity queries
- Proposed: O(1) average case
**Benchmark Targets:**
- 10,000 entities with 50 visible: <1ms per frame
- Entity collision detection: O(k) where k = entities in same hash cell
---
## Migration Path
### Phase 1: Performance Foundation (Issues #115, #116, #117)
**Backward compatible improvements:**
1. Add SpatialHash to existing UIGrid
2. Implement dirty flag system
3. Add memory pool for entities
**No breaking changes to Python API.**
### Phase 2: Multi-Tile Support (Issue #123)
**Add to existing UIEntity:**
```cpp
// Add to UIEntity class
sf::Vector2i dimensions = {1, 1}; // Default 1x1 (backward compatible)
std::set<sf::Vector2i> occupiedTiles;
void updateOccupiedTiles();
bool occupies(int x, int y) const;
```
**Python API additions:**
```python
# Backward compatible - existing code works unchanged
enemy = mcrfpy.Entity(grid_pos=(10, 10), sprite_index=5)
# New code can specify dimensions
dragon = mcrfpy.Entity(grid_pos=(20, 20), sprite_index=10, dimensions=(2, 2))
```
### Phase 3: Flexible Content (Issue #124)
**Replace UIEntity::sprite with content:**
```cpp
// Deprecate: UISprite sprite;
// Add: std::shared_ptr<UIDrawable> content;
// Backward compatibility shim:
PyObject* get_sprite() {
auto sprite = std::dynamic_pointer_cast<UISprite>(content);
if (!sprite) {
// Legacy: entity still has sprite member
return legacy_sprite_accessor();
}
return RET_PY_INSTANCE(sprite);
}
```
**Migration period:** 1-2 releases with deprecation warnings.
### Phase 4: Layer System
**Add GridLayer system alongside existing architecture:**
```cpp
// Existing entities automatically added to default "entities" layer
// New layer API available for advanced use
```
**Backward compatible - existing code works unchanged.**
---
## Use Cases Enabled
### Speech Bubbles
```python
speech = mcrfpy.Frame(size=(100, 40))
speech.append(mcrfpy.Caption(text="Hello adventurer!"))
bubble = mcrfpy.Entity(grid_pos=(npc.x, npc.y - 1))
bubble.content = speech
grid.entities.append(bubble)
```
### Large Enemies
```python
dragon = mcrfpy.Entity(grid_pos=(25, 25), sprite_index=DRAGON, dimensions=(3, 3))
# Pathfinding accounts for size
if grid.can_large_entity_move_to(dragon, new_x, new_y):
dragon.move_to(new_x, new_y)
```
### Weather Effects
```python
grid.add_layer("rain", mcrfpy.LayerType.EFFECT, z_order=200)
rain_layer = grid.get_layer("rain")
for i in range(100):
raindrop = mcrfpy.Entity(
grid_pos=(random.randint(0, 49), random.randint(0, 49)),
sprite_index=RAINDROP
)
rain_layer.entities.append(raindrop)
```
### Nested Mini-Map
```python
minimap = mcrfpy.Grid(grid_size=(20, 20), pos=(0, 0), size=(100, 100))
# ... populate minimap ...
minimap_entity = mcrfpy.Entity(grid_pos=(0, 0))
minimap_entity.content = minimap
hud_layer.entities.append(minimap_entity)
```
---
## Performance Expectations
### Before (Current System)
- 1,000 entities: 60 FPS
- 10,000 entities: 15 FPS (unacceptable)
- Entity query: O(n) = slow
### After (Proposed System)
- 1,000 entities: 60 FPS
- 10,000 entities: 60 FPS (with spatial hash + culling)
- Entity query: O(1) average case
### Memory Impact
- Per-entity overhead: +24 bytes (dimensions, occupied tiles set)
- Spatial hash: ~8KB for 1000 entities (negligible)
- Optional gridstate: Save width × height × sizeof(UIGridPointState) per decorative entity
---
## Open Questions
1. **Backward Compatibility Timeline**
- How many releases should deprecation period last?
- Support for legacy `entity.sprite` accessor?
2. **Layer API Design**
- Should layers have separate render textures?
- Layer blending modes (multiply, add, alpha)?
3. **Multi-Tile Pathfinding**
- Should large entities use separate TCOD maps?
- How to handle partially-blocked paths?
4. **Content Delegation**
- Should entity forward all UIDrawable methods to content?
- Or keep explicit `entity.content.method()` pattern?
---
## Implementation Complexity
**Estimated Effort:**
- Phase 1 (SpatialHash, dirty flags): 40-60 hours
- Phase 2 (Multi-tile): 20-30 hours
- Phase 3 (Flexible content): 30-40 hours
- Phase 4 (Layers): 40-50 hours
**Total:** 130-180 hours (3-4 months part-time)
**Risk Areas:**
- Backward compatibility testing
- Python binding complexity for flexible content
- Performance regression testing
- Documentation updates
---
## Decision Needed
**Should this proposal move forward?**
- ✅ Addresses major architectural limitations
- ✅ Enables modern roguelike features
- ✅ Clear migration path with backward compatibility
- ⚠️ Significant implementation effort
- ⚠️ Risk of introducing bugs in core systems
**Alternative:** Focus on incremental improvements (SpatialHash, dirty flags) without architectural redesign.
---
**Navigation:**
- [[Home]] - Documentation hub
- [[Grid-System]] - Current architecture
- [[Design-Proposals]] - All design proposals