McRogueFace/src/UIGridPoint.h
John McCardle 2ceb18e673 refactor(grid): GridData SoA -- dense uint8 planes for walkable/transparent; closes #332
Cell logic storage was an array-of-UIGridPoint: 24 bytes/cell for 2 bytes of
real payload (walkable/transparent bools), the other 22 being redundant grid_x/
grid_y and a parent_grid back-pointer. Grids larger than 64 in either dimension
switched to non-contiguous 64x64 chunk storage, so no whole-grid contiguous
view was possible at any size.

Replaced with two dense row-major std::vector<uint8_t> planes on GridData
(walkable_plane, transparent_plane), indexed y*grid_w + x, with inline
is/setWalkable / is/setTransparent accessors. Chunking is dropped for LOGIC data
entirely (render caches still chunk independently in GridLayers) -- so cells are
contiguous at any size. Result: 12x smaller logic layer (2 vs 24 B/cell) and
cache-friendly iteration; a 1024x1024 grid's logic data is 2 MB instead of 24 MB.

The Python GridPoint wrapper already held (grid, x, y) and re-resolved per
access, so it just calls the new accessors -- low-invasiveness. GridData::at()
-> UIGridPoint& is removed; its callers convert to accessors:
- UIGridPoint get/set_bool_member + repr (the wrapper)
- UIGridPyMethods apply-threshold / apply-ranges heightmap writers
- syncTCODMap / syncTCODMapCell (read planes)
- EntityBehavior::isCellWalkable and PathProvider::cellWalkable (pathfinding --
  both were missed by the first scout; found via exhaustive grep)
- UITestScene

UIGridPoint's per-cell data members + ctor removed (class now only namespaces
the Python type's accessors). GridChunk.h/.cpp and ChunkManager deleted (dead
after logic-chunking removal; nothing else referenced them). No walkable/
transparent serialization existed, so no format impact.

Unblocks #333 (TCOD map dedup) and a future contiguous numpy view of
walkable/transparent.

Regression test issue_332_soa_storage_test.py: round-trips walkable/transparent
on a 200x160 grid at and across the old 64-cell chunk boundary, default values,
toggle-off, subscript vs at() agreement, grid_pos/entities, and a per-cell
footprint guard. Suite 311/311 (FOV + pathfinding + heightmap unchanged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 01:43:30 -04:00

84 lines
3.4 KiB
C++

#pragma once
#include "Common.h"
#include "Python.h"
#include "structmember.h"
#include "IndexTexture.h"
#include "Resources.h"
#include <list>
#include "PyCallable.h"
#include "PyTexture.h"
#include "PyColor.h"
#include "PyVector.h"
#include "PyFont.h"
static PyObject* sfColor_to_PyObject(sf::Color color);
static sf::Color PyObject_to_sfColor(PyObject* obj);
class UIGrid;
class GridData;
class UIEntity;
class UIGridPoint;
typedef struct {
PyObject_HEAD
std::shared_ptr<UIGrid> grid;
int x, y; // Grid coordinates - compute data pointer on access
} PyUIGridPointObject;
// UIGridPoint - namespaces the Python GridPoint type's accessors.
// #150 - Layer-related properties (color, tilesprite, etc.) removed; now handled by layers.
// #332 - per-cell data (walkable/transparent) moved to GridData's dense uint8
// planes (SoA); this class no longer stores cell state. The Python wrapper
// (PyUIGridPointObject) holds (grid, x, y) and reads/writes via GridData accessors.
class UIGridPoint
{
public:
// Built-in property accessors (walkable, transparent only)
static PyGetSetDef getsetters[];
static int set_bool_member(PyUIGridPointObject* self, PyObject* value, void* closure);
static PyObject* get_bool_member(PyUIGridPointObject* self, void* closure);
static PyObject* repr(PyUIGridPointObject* self);
// #114 - entities property: list of entities at this cell
static PyObject* get_entities(PyUIGridPointObject* self, void* closure);
// #177 - grid_pos property: grid coordinates as tuple
static PyObject* get_grid_pos(PyUIGridPointObject* self, void* closure);
// #150 - Dynamic property access for named layers
static PyObject* getattro(PyUIGridPointObject* self, PyObject* name);
static int setattro(PyUIGridPointObject* self, PyObject* name, PyObject* value);
};
// UIGridPointState / PyUIGridPointStateType removed in #294.
// Per-entity visibility memory now lives on UIEntity::perspective_map as a
// DiscreteMap (3-state: 0=unknown, 1=discovered, 2=visible). See mcrfpy.Perspective.
namespace mcrfpydef {
// #189 - Use inline instead of static to ensure single instance across translation units
inline PyTypeObject PyUIGridPointType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.GridPoint",
.tp_basicsize = sizeof(PyUIGridPointObject),
.tp_itemsize = 0,
.tp_repr = (reprfunc)UIGridPoint::repr,
// #150 - Dynamic attribute access for named layers
.tp_getattro = (getattrofunc)UIGridPoint::getattro,
.tp_setattro = (setattrofunc)UIGridPoint::setattro,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR(
"GridPoint -- a single cell in a Grid.\n\n"
"Obtained via grid.at(x, y). Cannot be constructed directly.\n\n"
"Properties:\n"
" walkable (bool): Whether entities can traverse this cell.\n"
" transparent (bool): Whether this cell allows line-of-sight.\n"
" entities (list, read-only): Entities currently on this cell.\n"
" grid_pos (tuple, read-only): (x, y) position in the grid.\n\n"
"Named layer data is accessible as dynamic attributes.\n"
),
.tp_getset = UIGridPoint::getsetters,
//.tp_init = (initproc)PyUIGridPoint_init, // TODO Define the init function
.tp_new = NULL, // Prevent instantiation from Python - Issue #12
};
}