Table of Contents
- Proposal: Next-Generation Grid & Entity System
- Executive Summary
- Current Limitations
- 1. Entity Type Rigidity
- 2. Single-Tile Limitation
- 3. Fixed Layer System
- 4. Performance Issues
- 5. Memory Inefficiency
- Proposed Architecture
- Core Change 1: Flexible Entity Content
- Core Change 2: Multi-Tile Entities
- Core Change 3: Flexible Layer System
- Core Change 4: Spatial Optimization
- Migration Path
- Phase 1: Performance Foundation (Issues #115, #116, #117)
- Phase 2: Multi-Tile Support (Issue #123)
- Phase 3: Flexible Content (Issue #124)
- Phase 4: Layer System
- Use Cases Enabled
- Performance Expectations
- Open Questions
- Implementation Complexity
- Decision Needed
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 limitationsNEXT_GEN_GRIDS_ENTITIES_PROPOSAL.md- Detailed technical proposalNEXT_GEN_GRIDS_ENTITIES_IDEATION.md- Use cases and ideation
Related Issues:
- #115 - SpatialHash for 10,000+ entities
- #116 - Dirty flag system
- #113 - Batch operations
- #117 - Memory pool
- #123 - Subgrid system
- #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:
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/heightproperties- 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:
// 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
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:
# 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
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:
# 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
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:
# 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:
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:
- Add SpatialHash to existing UIGrid
- Implement dirty flag system
- Add memory pool for entities
No breaking changes to Python API.
Phase 2: Multi-Tile Support (Issue #123)
Add to existing UIEntity:
// 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:
# 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:
// 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:
// 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
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
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
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
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
-
Backward Compatibility Timeline
- How many releases should deprecation period last?
- Support for legacy
entity.spriteaccessor?
-
Layer API Design
- Should layers have separate render textures?
- Layer blending modes (multiply, add, alpha)?
-
Multi-Tile Pathfinding
- Should large entities use separate TCOD maps?
- How to handle partially-blocked paths?
-
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