Batch Operations for Grid #113

Closed
opened 2025-07-12 19:01:23 +00:00 by john · 4 comments
Owner

Add NumPy-style batch operations for efficient grid manipulation.

Definition of Done:

  • Context manager for batching grid updates
  • Significant performance improvement for large grid operations
  • Compatible with existing grid API
  • Documentation and examples

Wiki References:

Blocked by: #116 (Dirty Flag System) - batch operations will benefit significantly from dirty flag optimization

Add NumPy-style batch operations for efficient grid manipulation. **Definition of Done:** - Context manager for batching grid updates - Significant performance improvement for large grid operations - Compatible with existing grid API - Documentation and examples **Wiki References:** - [Grid System](https://gamedev.ffwf.net/gitea/john/McRogueFace/wiki/Grid-System) - [Grid Rendering Pipeline](https://gamedev.ffwf.net/gitea/john/McRogueFace/wiki/Grid-Rendering-Pipeline) - [Performance Optimization Workflow](https://gamedev.ffwf.net/gitea/john/McRogueFace/wiki/Performance-Optimization-Workflow) **Blocked by:** #116 (Dirty Flag System) - batch operations will benefit significantly from dirty flag optimization
john added the
Major Feature
label 2025-07-12 19:01:34 +00:00
john added the
priority:tier1-active
system:grid
labels 2025-10-26 00:48:36 +00:00
Author
Owner

FOV Data Access Patterns

Investigation of #146 revealed that compute_fov() has an O(n²) bug where it iterates the entire grid to build a return list. Once fixed, we need better patterns for bulk FOV access.

Use cases for FOV data:

  1. Rendering fog overlay - Already handled by the render loop checking is_in_fov() per visible cell. No batch API needed.

  2. Selecting visible entities - "Which entities can I see?"

    # Option A: Filter on entity collection
    visible = [e for e in grid.entities if grid.is_in_fov(e.grid_x, e.grid_y)]
    
    # Option B: Grid method
    visible = grid.entities_in_fov()  # Returns filtered EntityCollection
    
  3. Applying fog rules to a color layer - "Dim undiscovered cells"

    # With batch operations, something like:
    with grid.batch() as batch:
        for x, y in batch.cells_in_radius(cx, cy, radius):
            if not grid.is_in_fov(x, y):
                layer.set_color(x, y, dim_color)
    
  4. AI decision making - "What can this entity see?"

    • Usually queries specific cells, not bulk iteration
    • is_in_fov() is sufficient

Recommendation:

  • Keep compute_fov() as void (fix #146)
  • Add entities_in_fov() convenience method
  • Batch operations (#113) should include cells_in_radius(x, y, r) iterator for efficient radius-based operations
  • Consider discovered state tracking separate from per-frame visible state
## FOV Data Access Patterns Investigation of #146 revealed that `compute_fov()` has an O(n²) bug where it iterates the entire grid to build a return list. Once fixed, we need better patterns for bulk FOV access. **Use cases for FOV data:** 1. **Rendering fog overlay** - Already handled by the render loop checking `is_in_fov()` per visible cell. No batch API needed. 2. **Selecting visible entities** - "Which entities can I see?" ```python # Option A: Filter on entity collection visible = [e for e in grid.entities if grid.is_in_fov(e.grid_x, e.grid_y)] # Option B: Grid method visible = grid.entities_in_fov() # Returns filtered EntityCollection ``` 3. **Applying fog rules to a color layer** - "Dim undiscovered cells" ```python # With batch operations, something like: with grid.batch() as batch: for x, y in batch.cells_in_radius(cx, cy, radius): if not grid.is_in_fov(x, y): layer.set_color(x, y, dim_color) ``` 4. **AI decision making** - "What can this entity see?" - Usually queries specific cells, not bulk iteration - `is_in_fov()` is sufficient **Recommendation:** - Keep `compute_fov()` as void (fix #146) - Add `entities_in_fov()` convenience method - Batch operations (#113) should include `cells_in_radius(x, y, r)` iterator for efficient radius-based operations - Consider `discovered` state tracking separate from per-frame `visible` state
Author
Owner

perspective_enabled and set_perspective() may still exist but the FOV overlay isn't automatic anymore.

Proper exposure for FOV and pathfinding without requiring Python to receive huge arrays/lists of points is an important aspect for providing the right interface to these subsystems.

> `perspective_enabled` and `set_perspective()` may still exist but the FOV overlay isn't automatic anymore. Proper exposure for FOV and pathfinding without requiring Python to receive huge arrays/lists of points is an important aspect for providing the right interface to these subsystems.
Author
Owner

Refined Design - Batch Operations for Grid Layers

Core Problem Solved

The anti-pattern of O(n) Python/C++ boundary crossings:

# BAD: 30,000 calls for 100x100 grid
for y in range(100):
    for x in range(100):
        layer.set(x, y, some_value)  # C++ call per cell

New Layer Methods

ColorLayer:

layer.fill(color)                           # existing
layer.fill_rect(pos, size, color)           # NEW - single C++ call
layer.draw_fov(pos, fov=None, radius=None,  # NEW - one-time FOV draw
               visible=, discovered=, unknown=)

TileLayer:

layer.fill(index)                           # existing
layer.fill_rect(pos, size, index)           # NEW
layer.draw_fov(pos, fov=None, radius=None,  # NEW
               visible=, discovered=, unknown=)

Position Argument Pattern

All position/size arguments accept:

  • mcrfpy.Vector(x, y)
  • (x, y) tuple
  • [x, y] list

Internally converted via Vector normalization.

Anti-pattern (reject):

layer.fill_rect(10, 20, 100, 50, color)  # BAD: separate x, y, w, h

Correct:

layer.fill_rect((10, 20), (100, 50), color)  # GOOD: grouped pos, size

draw_fov() Specification

One-time FOV drawing without perspective binding:

layer.draw_fov(
    pos,                   # FOV center (Vector/tuple/list)
    fov=None,              # None = use grid.fov
    radius=None,           # None = use grid.fov_radius  
    visible=Color(0,0,0,0),
    discovered=Color(32,32,40,192),
    unknown=Color(0,0,0,255)
)
  • Computes FOV from pos using specified algorithm
  • Sets layer values based on visibility
  • Does NOT bind to perspective system (use apply_perspective() for that)

Dependencies

  • Blocked by: Nothing (#116 Dirty Flag is closed)
  • Blocks: #114 (Perspective System)
## Refined Design - Batch Operations for Grid Layers ### Core Problem Solved The anti-pattern of O(n) Python/C++ boundary crossings: ```python # BAD: 30,000 calls for 100x100 grid for y in range(100): for x in range(100): layer.set(x, y, some_value) # C++ call per cell ``` ### New Layer Methods **ColorLayer:** ```python layer.fill(color) # existing layer.fill_rect(pos, size, color) # NEW - single C++ call layer.draw_fov(pos, fov=None, radius=None, # NEW - one-time FOV draw visible=, discovered=, unknown=) ``` **TileLayer:** ```python layer.fill(index) # existing layer.fill_rect(pos, size, index) # NEW layer.draw_fov(pos, fov=None, radius=None, # NEW visible=, discovered=, unknown=) ``` ### Position Argument Pattern All position/size arguments accept: - `mcrfpy.Vector(x, y)` - `(x, y)` tuple - `[x, y]` list Internally converted via Vector normalization. **Anti-pattern (reject):** ```python layer.fill_rect(10, 20, 100, 50, color) # BAD: separate x, y, w, h ``` **Correct:** ```python layer.fill_rect((10, 20), (100, 50), color) # GOOD: grouped pos, size ``` ### draw_fov() Specification One-time FOV drawing without perspective binding: ```python layer.draw_fov( pos, # FOV center (Vector/tuple/list) fov=None, # None = use grid.fov radius=None, # None = use grid.fov_radius visible=Color(0,0,0,0), discovered=Color(32,32,40,192), unknown=Color(0,0,0,255) ) ``` - Computes FOV from `pos` using specified algorithm - Sets layer values based on visibility - Does NOT bind to perspective system (use `apply_perspective()` for that) ### Dependencies - Blocked by: Nothing (#116 Dirty Flag is closed) - Blocks: #114 (Perspective System)
Author
Owner

Commit 018e735 implements Phase 2 layer operations:

ColorLayer:

  • fill_rect(pos, size, color) - Fill rectangular region with a color
  • draw_fov(source, radius=None, fov=None, visible=None, discovered=None, unknown=None) - Paint FOV-based visibility

TileLayer:

  • fill_rect(pos, size, index) - Fill rectangular region with a tile index

Example usage:

import mcrfpy

grid = mcrfpy.Grid(grid_size=(20, 15))

# Create FOV visualization layer
fov_layer = grid.add_layer('color', z_index=-1)
fov_layer.fill((0, 0, 0, 255))  # Black = unknown

# Draw FOV from position (5, 7)
fov_layer.draw_fov(
    source=(5, 7),
    radius=10,
    fov=mcrfpy.FOV.SHADOW,
    visible=(255, 255, 200, 64),    # Light yellow for visible
    discovered=(100, 100, 100, 128), # Gray for previously seen
    unknown=(0, 0, 0, 255)           # Black for never seen
)

# Fill rectangle
fov_layer.fill_rect((0, 0), (5, 5), (255, 0, 0, 128))

Phase 3 items (apply_perspective, entity.updateVisibility, entity.visible_entities) still pending.

Commit 018e735 implements Phase 2 layer operations: **ColorLayer:** - `fill_rect(pos, size, color)` - Fill rectangular region with a color - `draw_fov(source, radius=None, fov=None, visible=None, discovered=None, unknown=None)` - Paint FOV-based visibility **TileLayer:** - `fill_rect(pos, size, index)` - Fill rectangular region with a tile index Example usage: ```python import mcrfpy grid = mcrfpy.Grid(grid_size=(20, 15)) # Create FOV visualization layer fov_layer = grid.add_layer('color', z_index=-1) fov_layer.fill((0, 0, 0, 255)) # Black = unknown # Draw FOV from position (5, 7) fov_layer.draw_fov( source=(5, 7), radius=10, fov=mcrfpy.FOV.SHADOW, visible=(255, 255, 200, 64), # Light yellow for visible discovered=(100, 100, 100, 128), # Gray for previously seen unknown=(0, 0, 0, 255) # Black for never seen ) # Fill rectangle fov_layer.fill_rect((0, 0), (5, 5), (255, 0, 0, 128)) ``` Phase 3 items (apply_perspective, entity.updateVisibility, entity.visible_entities) still pending.
john closed this issue 2025-12-01 20:55:26 +00:00
Sign in to join this conversation.
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: john/McRogueFace#113
No description provided.