diff --git a/src/GridChunk.cpp b/src/GridChunk.cpp index d98339b..dd72d42 100644 --- a/src/GridChunk.cpp +++ b/src/GridChunk.cpp @@ -9,7 +9,7 @@ // ============================================================================= GridChunk::GridChunk(int chunk_x, int chunk_y, int width, int height, - int world_x, int world_y, UIGrid* parent) + int world_x, int world_y, GridData* parent) : chunk_x(chunk_x), chunk_y(chunk_y), width(width), height(height), world_x(world_x), world_y(world_y), @@ -47,7 +47,7 @@ bool GridChunk::isVisible(float left_edge, float top_edge, // ChunkManager implementation // ============================================================================= -ChunkManager::ChunkManager(int grid_x, int grid_y, UIGrid* parent) +ChunkManager::ChunkManager(int grid_x, int grid_y, GridData* parent) : grid_x(grid_x), grid_y(grid_y), parent_grid(parent) { // Calculate number of chunks needed diff --git a/src/GridChunk.h b/src/GridChunk.h index 28ed1dd..909e4f0 100644 --- a/src/GridChunk.h +++ b/src/GridChunk.h @@ -6,6 +6,7 @@ // Forward declarations class UIGrid; +class GridData; class PyTexture; /** @@ -36,11 +37,11 @@ public: bool dirty; // Parent grid reference - UIGrid* parent_grid; + GridData* parent_grid; // Constructor GridChunk(int chunk_x, int chunk_y, int width, int height, - int world_x, int world_y, UIGrid* parent); + int world_x, int world_y, GridData* parent); // Access cell at local chunk coordinates UIGridPoint& at(int local_x, int local_y); @@ -69,10 +70,10 @@ public: std::vector> chunks; // Parent grid - UIGrid* parent_grid; + GridData* parent_grid; // Constructor - creates chunks for given grid dimensions - ChunkManager(int grid_x, int grid_y, UIGrid* parent); + ChunkManager(int grid_x, int grid_y, GridData* parent); // Get chunk containing cell (x, y) GridChunk* getChunkForCell(int x, int y); diff --git a/src/GridData.cpp b/src/GridData.cpp new file mode 100644 index 0000000..a972b6c --- /dev/null +++ b/src/GridData.cpp @@ -0,0 +1,184 @@ +// GridData.cpp - Pure data layer implementation (#252) +#include "GridData.h" +#include "UIEntity.h" +#include "PyTexture.h" +#include + +GridData::GridData() +{ + entities = std::make_shared>>(); + children = std::make_shared>>(); +} + +GridData::~GridData() +{ + cleanupTCOD(); +} + +void GridData::cleanupTCOD() +{ + dijkstra_maps.clear(); + if (tcod_map) { + delete tcod_map; + tcod_map = nullptr; + } +} + +void GridData::initStorage(int gx, int gy, GridData* parent_ref) +{ + grid_w = gx; + grid_h = gy; + use_chunks = (gx > CHUNK_THRESHOLD || gy > CHUNK_THRESHOLD); + + if (tcod_map) delete tcod_map; + tcod_map = new TCODMap(gx, gy); + + if (use_chunks) { + chunk_manager = std::make_unique(gx, gy, parent_ref); + for (int cy = 0; cy < chunk_manager->chunks_y; ++cy) { + for (int cx = 0; cx < chunk_manager->chunks_x; ++cx) { + GridChunk* chunk = chunk_manager->getChunk(cx, cy); + if (!chunk) continue; + for (int ly = 0; ly < chunk->height; ++ly) { + for (int lx = 0; lx < chunk->width; ++lx) { + auto& cell = chunk->at(lx, ly); + cell.grid_x = chunk->world_x + lx; + cell.grid_y = chunk->world_y + ly; + cell.parent_grid = parent_ref; + } + } + } + } + } else { + points.resize(gx * gy); + for (int y = 0; y < gy; y++) { + for (int x = 0; x < gx; x++) { + int idx = y * gx + x; + points[idx].grid_x = x; + points[idx].grid_y = y; + points[idx].parent_grid = parent_ref; + } + } + } + + syncTCODMap(); +} + +// Cell access +UIGridPoint& GridData::at(int x, int y) +{ + if (use_chunks && chunk_manager) { + return chunk_manager->at(x, y); + } + return points[y * grid_w + x]; +} + +// TCOD integration +void GridData::syncTCODMap() +{ + if (!tcod_map) return; + for (int y = 0; y < grid_h; y++) { + for (int x = 0; x < grid_w; x++) { + const UIGridPoint& point = at(x, y); + tcod_map->setProperties(x, y, point.transparent, point.walkable); + } + } + fov_dirty = true; +} + +void GridData::syncTCODMapCell(int x, int y) +{ + if (!tcod_map || x < 0 || x >= grid_w || y < 0 || y >= grid_h) return; + const UIGridPoint& point = at(x, y); + tcod_map->setProperties(x, y, point.transparent, point.walkable); + fov_dirty = true; +} + +void GridData::computeFOV(int x, int y, int radius, bool light_walls, TCOD_fov_algorithm_t algo) +{ + if (!tcod_map || x < 0 || x >= grid_w || y < 0 || y >= grid_h) return; + + if (!fov_dirty && + x == fov_last_x && y == fov_last_y && + radius == fov_last_radius && + light_walls == fov_last_light_walls && + algo == fov_last_algo) { + return; + } + + std::lock_guard lock(fov_mutex); + tcod_map->computeFov(x, y, radius, light_walls, algo); + + fov_dirty = false; + fov_last_x = x; + fov_last_y = y; + fov_last_radius = radius; + fov_last_light_walls = light_walls; + fov_last_algo = algo; +} + +bool GridData::isInFOV(int x, int y) const +{ + if (!tcod_map || x < 0 || x >= grid_w || y < 0 || y >= grid_h) return false; + std::lock_guard lock(fov_mutex); + return tcod_map->isInFov(x, y); +} + +// Layer management +std::shared_ptr GridData::addColorLayer(int z_index, const std::string& name) +{ + auto layer = std::make_shared(z_index, grid_w, grid_h, this); + layer->name = name; + layers.push_back(layer); + layers_need_sort = true; + return layer; +} + +std::shared_ptr GridData::addTileLayer(int z_index, std::shared_ptr texture, const std::string& name) +{ + auto layer = std::make_shared(z_index, grid_w, grid_h, this, texture); + layer->name = name; + layers.push_back(layer); + layers_need_sort = true; + return layer; +} + +void GridData::removeLayer(std::shared_ptr layer) +{ + auto it = std::find(layers.begin(), layers.end(), layer); + if (it != layers.end()) { + layers.erase(it); + } + if (layer) { + layer->parent_grid = nullptr; + } +} + +void GridData::sortLayers() +{ + if (layers_need_sort) { + std::sort(layers.begin(), layers.end(), + [](const auto& a, const auto& b) { return a->z_index < b->z_index; }); + layers_need_sort = false; + } +} + +std::shared_ptr GridData::getLayerByName(const std::string& name) +{ + if (name.empty()) return nullptr; + for (auto& layer : layers) { + if (layer->name == name) return layer; + } + return nullptr; +} + +bool GridData::isProtectedLayerName(const std::string& name) +{ + static const std::vector protected_names = { + "walkable", "transparent" + }; + for (const auto& pn : protected_names) { + if (name == pn) return true; + } + return false; +} diff --git a/src/GridData.h b/src/GridData.h new file mode 100644 index 0000000..89dce0e --- /dev/null +++ b/src/GridData.h @@ -0,0 +1,131 @@ +#pragma once +// GridData.h - Pure data layer for grid state (#252) +// +// GridData holds all non-rendering grid state: cells, entities, TCOD map, +// spatial hash, layers, FOV, pathfinding caches. UIGrid inherits from this +// and adds rendering. GridView can also reference GridData for multi-view. + +#include "Common.h" +#include "Python.h" +#include +#include +#include +#include +#include +#include +#include + +#include "PyCallable.h" +#include "UIGridPoint.h" +#include "SpatialHash.h" +#include "GridLayers.h" +#include "GridChunk.h" + +// Forward declarations +class DijkstraMap; +class UIEntity; +class UIDrawable; +class PyTexture; + +class GridData { +public: + GridData(); + virtual ~GridData(); + + // ========================================================================= + // Grid dimensions and cell storage + // ========================================================================= + int grid_w = 0, grid_h = 0; + + // #123 - Chunk-based storage for large grid support + std::unique_ptr chunk_manager; + // Legacy flat storage (kept for small grids or compatibility) + std::vector points; + // Use chunks for grids larger than this threshold + static constexpr int CHUNK_THRESHOLD = 64; + bool use_chunks = false; + + // Cell access (handles both flat and chunked storage) + UIGridPoint& at(int x, int y); + + // ========================================================================= + // Entity management + // ========================================================================= + std::shared_ptr>> entities; + SpatialHash spatial_hash; // O(1) entity queries (#115) + + // ========================================================================= + // TCOD integration (FOV and pathfinding base) + // ========================================================================= + TCODMap* tcod_map = nullptr; + mutable std::mutex fov_mutex; + + void syncTCODMap(); + void syncTCODMapCell(int x, int y); + void computeFOV(int x, int y, int radius, bool light_walls = true, + TCOD_fov_algorithm_t algo = FOV_BASIC); + bool isInFOV(int x, int y) const; + TCODMap* getTCODMap() const { return tcod_map; } + + // #114 - FOV algorithm and radius defaults + TCOD_fov_algorithm_t fov_algorithm = FOV_BASIC; + int fov_radius = 10; + + // #292 - FOV deduplication + bool fov_dirty = true; + int fov_last_x = -1, fov_last_y = -1; + int fov_last_radius = -1; + bool fov_last_light_walls = true; + TCOD_fov_algorithm_t fov_last_algo = FOV_BASIC; + + // ========================================================================= + // Pathfinding caches + // ========================================================================= + std::map, std::shared_ptr> dijkstra_maps; + + // ========================================================================= + // Layer system (#147, #150) + // ========================================================================= + std::vector> layers; + bool layers_need_sort = true; + + std::shared_ptr addColorLayer(int z_index, const std::string& name = ""); + std::shared_ptr addTileLayer(int z_index, std::shared_ptr texture = nullptr, + const std::string& name = ""); + void removeLayer(std::shared_ptr layer); + void sortLayers(); + std::shared_ptr getLayerByName(const std::string& name); + static bool isProtectedLayerName(const std::string& name); + + // ========================================================================= + // Cell callbacks (#142, #230) + // ========================================================================= + std::unique_ptr on_cell_enter_callable; + std::unique_ptr on_cell_exit_callable; + std::unique_ptr on_cell_click_callable; + std::optional hovered_cell; + std::optional last_clicked_cell; + + struct CellCallbackCache { + uint32_t generation = 0; + bool valid = false; + bool has_on_cell_click = false; + bool has_on_cell_enter = false; + bool has_on_cell_exit = false; + }; + CellCallbackCache cell_callback_cache; + + // fireCellClick/Enter/Exit and refreshCellCallbackCache are on UIGrid + // because they need access to UIDrawable::serial_number/is_python_subclass + + // ========================================================================= + // UIDrawable children (speech bubbles, effects, overlays) + // ========================================================================= + std::shared_ptr>> children; + bool children_need_sort = true; + +protected: + // Initialize grid storage (flat or chunked) and TCOD map + void initStorage(int gx, int gy, GridData* parent_ref); + void cleanupTCOD(); +}; diff --git a/src/GridLayers.cpp b/src/GridLayers.cpp index cdc069c..6f6dd56 100644 --- a/src/GridLayers.cpp +++ b/src/GridLayers.cpp @@ -150,7 +150,7 @@ static sf::Color LerpColor(const sf::Color& a, const sf::Color& b, float t) { // GridLayer base class // ============================================================================= -GridLayer::GridLayer(GridLayerType type, int z_index, int grid_x, int grid_y, UIGrid* parent) +GridLayer::GridLayer(GridLayerType type, int z_index, int grid_x, int grid_y, GridData* parent) : type(type), z_index(z_index), grid_x(grid_x), grid_y(grid_y), parent_grid(parent), visible(true), chunks_x(0), chunks_y(0), @@ -244,7 +244,7 @@ void GridLayer::ensureChunkTexture(int chunk_idx, int cell_width, int cell_heigh // ColorLayer implementation // ============================================================================= -ColorLayer::ColorLayer(int z_index, int grid_x, int grid_y, UIGrid* parent) +ColorLayer::ColorLayer(int z_index, int grid_x, int grid_y, GridData* parent) : GridLayer(GridLayerType::Color, z_index, grid_x, grid_y, parent), colors(grid_x * grid_y, sf::Color::Transparent), perspective_visible(255, 255, 200, 64), @@ -515,7 +515,7 @@ void ColorLayer::render(sf::RenderTarget& target, // TileLayer implementation // ============================================================================= -TileLayer::TileLayer(int z_index, int grid_x, int grid_y, UIGrid* parent, +TileLayer::TileLayer(int z_index, int grid_x, int grid_y, GridData* parent, std::shared_ptr texture) : GridLayer(GridLayerType::Tile, z_index, grid_x, grid_y, parent), tiles(grid_x * grid_y, -1), // -1 = no tile diff --git a/src/GridLayers.h b/src/GridLayers.h index 8ed414c..9597a30 100644 --- a/src/GridLayers.h +++ b/src/GridLayers.h @@ -9,6 +9,7 @@ // Forward declarations class UIGrid; +class GridData; class PyTexture; class UIEntity; @@ -31,7 +32,7 @@ public: std::string name; // #150 - Layer name for GridPoint property access int z_index; // Negative = below entities, >= 0 = above entities int grid_x, grid_y; // Dimensions - UIGrid* parent_grid; // Parent grid reference + GridData* parent_grid; // Parent grid reference (#252: GridData, not UIGrid) bool visible; // Visibility flag // Chunk dimensions @@ -43,7 +44,7 @@ public: std::vector chunk_texture_initialized; // Track which textures are created int cached_cell_width, cached_cell_height; // Cell size used for cached textures - GridLayer(GridLayerType type, int z_index, int grid_x, int grid_y, UIGrid* parent); + GridLayer(GridLayerType type, int z_index, int grid_x, int grid_y, GridData* parent); virtual ~GridLayer() = default; // Mark entire layer as needing re-render @@ -93,7 +94,7 @@ public: sf::Color perspective_unknown; bool has_perspective; - ColorLayer(int z_index, int grid_x, int grid_y, UIGrid* parent); + ColorLayer(int z_index, int grid_x, int grid_y, GridData* parent); // Access color at position sf::Color& at(int x, int y); @@ -145,7 +146,7 @@ public: std::vector tiles; // Sprite indices (-1 = no tile) std::shared_ptr texture; - TileLayer(int z_index, int grid_x, int grid_y, UIGrid* parent, + TileLayer(int z_index, int grid_x, int grid_y, GridData* parent, std::shared_ptr texture = nullptr); // Access tile index at position diff --git a/src/UIGrid.cpp b/src/UIGrid.cpp index 93b8928..25aac83 100644 --- a/src/UIGrid.cpp +++ b/src/UIGrid.cpp @@ -22,17 +22,11 @@ // UIEntityCollection code moved to UIEntityCollection.cpp UIGrid::UIGrid() -: grid_w(0), grid_h(0), zoom(1.0f), center_x(0.0f), center_y(0.0f), ptex(nullptr), - fill_color(8, 8, 8, 255), tcod_map(nullptr), - perspective_enabled(false), fov_algorithm(FOV_BASIC), fov_radius(10), - use_chunks(false) // Default to omniscient view +: GridData(), // Initialize data layer (entities, children, FOV defaults) + zoom(1.0f), center_x(0.0f), center_y(0.0f), ptex(nullptr), + fill_color(8, 8, 8, 255), + perspective_enabled(false) { - // Initialize entities list - entities = std::make_shared>>(); - - // Initialize children collection (for UIDrawables like speech bubbles, effects) - children = std::make_shared>>(); - // Initialize box with safe defaults box.setSize(sf::Vector2f(0, 0)); position = sf::Vector2f(0, 0); // Set base class position @@ -53,12 +47,11 @@ UIGrid::UIGrid() } UIGrid::UIGrid(int gx, int gy, std::shared_ptr _ptex, sf::Vector2f _xy, sf::Vector2f _wh) -: grid_w(gx), grid_h(gy), +: GridData(), // Initialize data layer zoom(1.0f), ptex(_ptex), - fill_color(8, 8, 8, 255), tcod_map(nullptr), - perspective_enabled(false), fov_algorithm(FOV_BASIC), fov_radius(10), - use_chunks(gx > CHUNK_THRESHOLD || gy > CHUNK_THRESHOLD) // #123 - Use chunks for large grids + fill_color(8, 8, 8, 255), + perspective_enabled(false) { // Use texture dimensions if available, otherwise use defaults int cell_width = _ptex ? _ptex->sprite_width : DEFAULT_CELL_WIDTH; @@ -66,10 +59,6 @@ UIGrid::UIGrid(int gx, int gy, std::shared_ptr _ptex, sf::Vector2f _x center_x = (gx/2) * cell_width; center_y = (gy/2) * cell_height; - entities = std::make_shared>>(); - - // Initialize children collection (for UIDrawables like speech bubbles, effects) - children = std::make_shared>>(); box.setSize(_wh); position = _xy; // Set base class position @@ -91,47 +80,8 @@ UIGrid::UIGrid(int gx, int gy, std::shared_ptr _ptex, sf::Vector2f _x // textures are upside-down inside renderTexture output.setTexture(renderTexture.getTexture()); - // Create TCOD map for FOV and as source for pathfinding - tcod_map = new TCODMap(gx, gy); - // Note: DijkstraMap objects are created on-demand via get_dijkstra_map() - // A* paths are computed on-demand via find_path() - - // #123 - Initialize storage based on grid size - if (use_chunks) { - // Large grid: use chunk-based storage - chunk_manager = std::make_unique(gx, gy, this); - - // Initialize all cells with parent reference - for (int cy = 0; cy < chunk_manager->chunks_y; ++cy) { - for (int cx = 0; cx < chunk_manager->chunks_x; ++cx) { - GridChunk* chunk = chunk_manager->getChunk(cx, cy); - if (!chunk) continue; - - for (int ly = 0; ly < chunk->height; ++ly) { - for (int lx = 0; lx < chunk->width; ++lx) { - auto& cell = chunk->at(lx, ly); - cell.grid_x = chunk->world_x + lx; - cell.grid_y = chunk->world_y + ly; - cell.parent_grid = this; - } - } - } - } - } else { - // Small grid: use flat storage (original behavior) - points.resize(gx * gy); - for (int y = 0; y < gy; y++) { - for (int x = 0; x < gx; x++) { - int idx = y * gx + x; - points[idx].grid_x = x; - points[idx].grid_y = y; - points[idx].parent_grid = this; - } - } - } - - // Initial sync of TCOD map - syncTCODMap(); + // Initialize grid storage, TCOD map, and sync (#252: delegated to GridData) + initStorage(gx, gy, static_cast(this)); } void UIGrid::update() {} @@ -467,24 +417,12 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target) } } -UIGridPoint& UIGrid::at(int x, int y) -{ - // #123 - Route through chunk manager for large grids - if (use_chunks && chunk_manager) { - return chunk_manager->at(x, y); - } - return points[y * grid_w + x]; -} +// at(), syncTCODMap(), computeFOV(), isInFOV(), layer management methods +// are now in GridData.cpp (#252) UIGrid::~UIGrid() { - // Clear Dijkstra maps first (they reference tcod_map) - dijkstra_maps.clear(); - - if (tcod_map) { - delete tcod_map; - tcod_map = nullptr; - } + // GridData destructor handles TCOD map and Dijkstra cleanup } void UIGrid::ensureRenderTextureSize() @@ -512,114 +450,6 @@ PyObjectsEnum UIGrid::derived_type() return PyObjectsEnum::UIGRID; } -// #147 - Layer management methods -std::shared_ptr UIGrid::addColorLayer(int z_index, const std::string& name) { - auto layer = std::make_shared(z_index, grid_w, grid_h, this); - layer->name = name; - layers.push_back(layer); - layers_need_sort = true; - return layer; -} - -std::shared_ptr UIGrid::addTileLayer(int z_index, std::shared_ptr texture, const std::string& name) { - auto layer = std::make_shared(z_index, grid_w, grid_h, this, texture); - layer->name = name; - layers.push_back(layer); - layers_need_sort = true; - return layer; -} - -std::shared_ptr UIGrid::getLayerByName(const std::string& name) { - for (auto& layer : layers) { - if (layer->name == name) { - return layer; - } - } - return nullptr; -} - -bool UIGrid::isProtectedLayerName(const std::string& name) { - // #150 - These names are reserved for GridPoint pathfinding properties - static const std::vector protected_names = { - "walkable", "transparent" - }; - for (const auto& pn : protected_names) { - if (name == pn) return true; - } - return false; -} - -void UIGrid::removeLayer(std::shared_ptr layer) { - auto it = std::find(layers.begin(), layers.end(), layer); - if (it != layers.end()) { - layers.erase(it); - } -} - -void UIGrid::sortLayers() { - if (layers_need_sort) { - std::sort(layers.begin(), layers.end(), - [](const auto& a, const auto& b) { return a->z_index < b->z_index; }); - layers_need_sort = false; - } -} - -// TCOD integration methods -void UIGrid::syncTCODMap() -{ - if (!tcod_map) return; - - for (int y = 0; y < grid_h; y++) { - for (int x = 0; x < grid_w; x++) { - const UIGridPoint& point = at(x, y); - tcod_map->setProperties(x, y, point.transparent, point.walkable); - } - } - fov_dirty = true; // #292: map changed, FOV needs recomputation -} - -void UIGrid::syncTCODMapCell(int x, int y) -{ - if (!tcod_map || x < 0 || x >= grid_w || y < 0 || y >= grid_h) return; - - const UIGridPoint& point = at(x, y); - tcod_map->setProperties(x, y, point.transparent, point.walkable); - fov_dirty = true; // #292: cell changed, FOV needs recomputation -} - -void UIGrid::computeFOV(int x, int y, int radius, bool light_walls, TCOD_fov_algorithm_t algo) -{ - if (!tcod_map || x < 0 || x >= grid_w || y < 0 || y >= grid_h) return; - - // #292: Skip redundant FOV computation if map hasn't changed and params match - if (!fov_dirty && - x == fov_last_x && y == fov_last_y && - radius == fov_last_radius && - light_walls == fov_last_light_walls && - algo == fov_last_algo) { - return; - } - - std::lock_guard lock(fov_mutex); - tcod_map->computeFov(x, y, radius, light_walls, algo); - - // Cache parameters for deduplication - fov_dirty = false; - fov_last_x = x; - fov_last_y = y; - fov_last_radius = radius; - fov_last_light_walls = light_walls; - fov_last_algo = algo; -} - -bool UIGrid::isInFOV(int x, int y) const -{ - if (!tcod_map || x < 0 || x >= grid_w || y < 0 || y >= grid_h) return false; - - std::lock_guard lock(fov_mutex); - return tcod_map->isInFov(x, y); -} - // Pathfinding methods moved to UIGridPathfinding.cpp // - Grid.find_path() returns AStarPath objects // - Grid.get_dijkstra_map() returns DijkstraMap objects (cached) diff --git a/src/UIGrid.h b/src/UIGrid.h index 2e7df32..f1fb1b5 100644 --- a/src/UIGrid.h +++ b/src/UIGrid.h @@ -26,169 +26,82 @@ #include "GridChunk.h" #include "SpatialHash.h" #include "UIEntityCollection.h" // EntityCollection types (extracted from UIGrid) +#include "GridData.h" // #252 - Data layer base class // Forward declaration for pathfinding class DijkstraMap; -class UIGrid: public UIDrawable +// UIGrid inherits both UIDrawable (rendering) and GridData (state). +// This allows GridData to be shared with GridView for multi-view support (#252). +class UIGrid: public UIDrawable, public GridData { private: std::shared_ptr ptex; // Default cell dimensions when no texture is provided static constexpr int DEFAULT_CELL_WIDTH = 16; static constexpr int DEFAULT_CELL_HEIGHT = 16; - TCODMap* tcod_map; // TCOD map for FOV and pathfinding - mutable std::mutex fov_mutex; // Mutex for thread-safe FOV operations - -public: - // Dijkstra map cache - keyed by root position - // Public so UIGridPathfinding can access it - std::map, std::shared_ptr> dijkstra_maps; public: UIGrid(); - //UIGrid(int, int, IndexTexture*, float, float, float, float); UIGrid(int, int, std::shared_ptr, sf::Vector2f, sf::Vector2f); - ~UIGrid(); // Destructor to clean up TCOD map + ~UIGrid(); void update(); void render(sf::Vector2f, sf::RenderTarget&) override final; - UIGridPoint& at(int, int); PyObjectsEnum derived_type() override final; - //void setSprite(int); virtual UIDrawable* click_at(sf::Vector2f point) override final; - - // TCOD integration methods - void syncTCODMap(); // Sync entire map with current grid state - void syncTCODMapCell(int x, int y); // Sync a single cell to TCOD map - void computeFOV(int x, int y, int radius, bool light_walls = true, TCOD_fov_algorithm_t algo = FOV_BASIC); - bool isInFOV(int x, int y) const; - TCODMap* getTCODMap() const { return tcod_map; } // Access for pathfinding - - // Pathfinding - new API creates AStarPath/DijkstraMap objects - // See UIGridPathfinding.h for the new pathfinding API - // Grid.find_path() now returns AStarPath objects - // Grid.get_dijkstra_map() returns DijkstraMap objects (cached by root position) - + // Phase 1 virtual method implementations sf::FloatRect get_bounds() const override; void move(float dx, float dy) override; void resize(float w, float h) override; void onPositionChanged() override; - int grid_w, grid_h; - //int grid_size; // grid sizes are implied by IndexTexture now + // ========================================================================= + // Rendering-only members (NOT in GridData) + // ========================================================================= sf::RectangleShape box; float center_x, center_y, zoom; - float camera_rotation = 0.0f; // Rotation of grid contents around camera center (degrees) - //IndexTexture* itex; + float camera_rotation = 0.0f; std::shared_ptr getTexture(); sf::Sprite sprite, output; sf::RenderTexture renderTexture; - sf::Vector2u renderTextureSize{0, 0}; // Track current allocation for resize detection - - // Helper to ensure renderTexture matches game resolution + sf::Vector2u renderTextureSize{0, 0}; void ensureRenderTextureSize(); - - // Intermediate texture for camera_rotation (larger than viewport to hold rotated content) sf::RenderTexture rotationTexture; - unsigned int rotationTextureSize = 0; // Track current allocation size - - // #123 - Chunk-based storage for large grid support - std::unique_ptr chunk_manager; - // Legacy flat storage (kept for small grids or compatibility) - std::vector points; - // Use chunks for grids larger than this threshold - static constexpr int CHUNK_THRESHOLD = 64; - bool use_chunks; - - std::shared_ptr>> entities; - - // Spatial hash for O(1) entity queries (#115) - SpatialHash spatial_hash; - - // UIDrawable children collection (speech bubbles, effects, overlays, etc.) - std::shared_ptr>> children; - bool children_need_sort = true; // Dirty flag for z_index sorting - - // Dynamic layer system (#147) - std::vector> layers; - bool layers_need_sort = true; // Dirty flag for z_index sorting - - // Layer management (#150 - extended with names) - std::shared_ptr addColorLayer(int z_index, const std::string& name = ""); - std::shared_ptr addTileLayer(int z_index, std::shared_ptr texture = nullptr, const std::string& name = ""); - void removeLayer(std::shared_ptr layer); - void sortLayers(); - std::shared_ptr getLayerByName(const std::string& name); - - // #150 - Protected layer names (reserved for GridPoint properties) - static bool isProtectedLayerName(const std::string& name); + unsigned int rotationTextureSize = 0; // Background rendering sf::Color fill_color; - // Perspective system - entity whose view to render - std::weak_ptr perspective_entity; // Weak reference to perspective entity - bool perspective_enabled; // Whether to use perspective rendering + // Perspective system + std::weak_ptr perspective_entity; + bool perspective_enabled; - // #114 - FOV algorithm and radius for this grid - TCOD_fov_algorithm_t fov_algorithm; // Default FOV algorithm (from mcrfpy.default_fov) - int fov_radius; // Default FOV radius - - // #292 - FOV deduplication: skip redundant computations - bool fov_dirty = true; // Set true when TCOD map changes - int fov_last_x = -1, fov_last_y = -1; // Last FOV computation parameters - int fov_last_radius = -1; - bool fov_last_light_walls = true; - TCOD_fov_algorithm_t fov_last_algo = FOV_BASIC; - - // #142, #230 - Grid cell mouse events - // Cell hover callbacks take only (cell_pos); cell click still takes (cell_pos, button, action) - std::unique_ptr on_cell_enter_callable; - std::unique_ptr on_cell_exit_callable; - std::unique_ptr on_cell_click_callable; - std::optional hovered_cell; // Currently hovered cell or nullopt - std::optional last_clicked_cell; // Cell clicked during click_at - - // Grid-specific cell callback cache (separate from UIDrawable::CallbackCache) - struct CellCallbackCache { - uint32_t generation = 0; - bool valid = false; - bool has_on_cell_click = false; - bool has_on_cell_enter = false; - bool has_on_cell_exit = false; - }; - CellCallbackCache cell_callback_cache; - - // #142 - Cell coordinate conversion (screen pos -> cell coords) - std::optional screenToCell(sf::Vector2f screen_pos) const; - - // #221 - Get effective cell size (texture size * zoom) - sf::Vector2f getEffectiveCellSize() const; - - // #142 - Update cell hover state (called from PyScene) - // Now takes button/action for consistent callback signatures - void updateCellHover(sf::Vector2f mousepos, const std::string& button, const std::string& action); - - // Fire cell callbacks - // #230: Cell hover callbacks (enter/exit) now take only (cell_pos) - // Cell click still takes (cell_pos, button, action) - // Returns true if a callback was fired + // Cell callback firing (needs UIDrawable::is_python_subclass, serial_number) bool fireCellClick(sf::Vector2i cell, const std::string& button, const std::string& action); bool fireCellEnter(sf::Vector2i cell); bool fireCellExit(sf::Vector2i cell); - - // Refresh cell callback cache for subclass method support void refreshCellCallbackCache(PyObject* pyObj); - + + // #142 - Cell coordinate conversion (needs texture for cell size) + std::optional screenToCell(sf::Vector2f screen_pos) const; + sf::Vector2f getEffectiveCellSize() const; + void updateCellHover(sf::Vector2f mousepos, const std::string& button, const std::string& action); + // Property system for animations bool setProperty(const std::string& name, float value) override; bool setProperty(const std::string& name, const sf::Vector2f& value) override; bool getProperty(const std::string& name, float& value) const override; bool getProperty(const std::string& name, sf::Vector2f& value) const override; - bool hasProperty(const std::string& name) const override; + // #169 - Camera positioning + void center_camera(); + void center_camera(float tile_x, float tile_y); + + // ========================================================================= + // Python API (static methods) + // ========================================================================= static int init(PyUIGridObject* self, PyObject* args, PyObject* kwds); static PyObject* get_grid_size(PyUIGridObject* self, void* closure); static PyObject* get_grid_w(PyUIGridObject* self, void* closure); @@ -215,35 +128,22 @@ public: static PyObject* py_at(PyUIGridObject* self, PyObject* args, PyObject* kwds); static PyObject* py_compute_fov(PyUIGridObject* self, PyObject* args, PyObject* kwds); static PyObject* py_is_in_fov(PyUIGridObject* self, PyObject* args, PyObject* kwds); - // Pathfinding methods moved to UIGridPathfinding.cpp - // py_find_path -> UIGridPathfinding::Grid_find_path (returns AStarPath) - // py_get_dijkstra_map -> UIGridPathfinding::Grid_get_dijkstra_map (returns DijkstraMap) - // py_clear_dijkstra_maps -> UIGridPathfinding::Grid_clear_dijkstra_maps - static PyObject* py_entities_in_radius(PyUIGridObject* self, PyObject* args, PyObject* kwds); // #115 - static PyObject* py_center_camera(PyUIGridObject* self, PyObject* args); // #169 + static PyObject* py_entities_in_radius(PyUIGridObject* self, PyObject* args, PyObject* kwds); + static PyObject* py_center_camera(PyUIGridObject* self, PyObject* args); static PyObject* get_camera_rotation(PyUIGridObject* self, void* closure); static int set_camera_rotation(PyUIGridObject* self, PyObject* value, void* closure); - - // #199 - HeightMap application methods static PyObject* py_apply_threshold(PyUIGridObject* self, PyObject* args, PyObject* kwds); static PyObject* py_apply_ranges(PyUIGridObject* self, PyObject* args); - - // #169 - Camera positioning - void center_camera(); // Center on grid's middle tile - void center_camera(float tile_x, float tile_y); // Center on specific tile - - // #301 - Turn management static PyObject* py_step(PyUIGridObject* self, PyObject* args, PyObject* kwds); static PyMethodDef methods[]; static PyGetSetDef getsetters[]; - static PyMappingMethods mpmethods; // For grid[x, y] subscript access - static PyObject* subscript(PyUIGridObject* self, PyObject* key); // __getitem__ + static PyMappingMethods mpmethods; + static PyObject* subscript(PyUIGridObject* self, PyObject* key); static PyObject* get_entities(PyUIGridObject* self, void* closure); static PyObject* get_children(PyUIGridObject* self, void* closure); static PyObject* repr(PyUIGridObject* self); - // #142 - Grid cell mouse event Python API static PyObject* get_on_cell_enter(PyUIGridObject* self, void* closure); static int set_on_cell_enter(PyUIGridObject* self, PyObject* value, void* closure); static PyObject* get_on_cell_exit(PyUIGridObject* self, void* closure); @@ -252,7 +152,6 @@ public: static int set_on_cell_click(PyUIGridObject* self, PyObject* value, void* closure); static PyObject* get_hovered_cell(PyUIGridObject* self, void* closure); - // #147 - Layer system Python API static PyObject* py_add_layer(PyUIGridObject* self, PyObject* args); static PyObject* py_remove_layer(PyUIGridObject* self, PyObject* args); static PyObject* get_layers(PyUIGridObject* self, void* closure); @@ -285,7 +184,7 @@ namespace mcrfpydef { obj->data->on_enter_unregister(); obj->data->on_exit_unregister(); obj->data->on_move_unregister(); - // Grid-specific cell callbacks + // Grid-specific cell callbacks (now on GridData base) obj->data->on_cell_enter_callable.reset(); obj->data->on_cell_exit_callable.reset(); obj->data->on_cell_click_callable.reset(); @@ -294,7 +193,7 @@ namespace mcrfpydef { Py_TYPE(self)->tp_free(self); }, .tp_repr = (reprfunc)UIGrid::repr, - .tp_as_mapping = &UIGrid::mpmethods, // Enable grid[x, y] subscript access + .tp_as_mapping = &UIGrid::mpmethods, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, .tp_doc = PyDoc_STR("Grid(pos=None, size=None, grid_size=None, texture=None, **kwargs)\n\n" "A grid-based UI element for tile-based rendering and entity management.\n\n" @@ -347,11 +246,9 @@ namespace mcrfpydef { " margin (float): General margin for alignment\n" " horiz_margin (float): Horizontal margin override\n" " vert_margin (float): Vertical margin override"), - // tp_traverse visits Python object references for GC cycle detection .tp_traverse = [](PyObject* self, visitproc visit, void* arg) -> int { PyUIGridObject* obj = (PyUIGridObject*)self; if (obj->data) { - // Base class callbacks if (obj->data->click_callable) { PyObject* callback = obj->data->click_callable->borrow(); if (callback && callback != Py_None) Py_VISIT(callback); @@ -368,7 +265,6 @@ namespace mcrfpydef { PyObject* callback = obj->data->on_move_callable->borrow(); if (callback && callback != Py_None) Py_VISIT(callback); } - // Grid-specific cell callbacks if (obj->data->on_cell_enter_callable) { PyObject* callback = obj->data->on_cell_enter_callable->borrow(); if (callback && callback != Py_None) Py_VISIT(callback); @@ -384,7 +280,6 @@ namespace mcrfpydef { } return 0; }, - // tp_clear breaks reference cycles by clearing Python references .tp_clear = [](PyObject* self) -> int { PyUIGridObject* obj = (PyUIGridObject*)self; if (obj->data) { @@ -399,7 +294,6 @@ namespace mcrfpydef { return 0; }, .tp_methods = UIGrid_all_methods, - //.tp_members = UIGrid::members, .tp_getset = UIGrid::getsetters, .tp_base = &mcrfpydef::PyDrawableType, .tp_init = (initproc)UIGrid::init, @@ -410,7 +304,4 @@ namespace mcrfpydef { return (PyObject*)self; } }; - - // EntityCollection types moved to UIEntityCollection.h - } diff --git a/src/UIGridPoint.h b/src/UIGridPoint.h index 902df2d..3121da1 100644 --- a/src/UIGridPoint.h +++ b/src/UIGridPoint.h @@ -16,6 +16,7 @@ static PyObject* sfColor_to_PyObject(sf::Color color); static sf::Color PyObject_to_sfColor(PyObject* obj); class UIGrid; +class GridData; class UIEntity; class UIGridPoint; class UIGridPointState; @@ -40,7 +41,7 @@ class UIGridPoint public: bool walkable, transparent; // Pathfinding/FOV properties int grid_x, grid_y; // Position in parent grid - UIGrid* parent_grid; // Parent grid reference for TCOD sync + GridData* parent_grid; // Parent grid reference for TCOD sync (#252) UIGridPoint(); // Built-in property accessors (walkable, transparent only)