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>
This commit is contained in:
parent
5b2a412c21
commit
2ceb18e673
13 changed files with 138 additions and 395 deletions
|
|
@ -5,9 +5,9 @@
|
|||
#include "McRFPy_Doc.h" // #177 - for MCRF_PROPERTY macro
|
||||
#include <cstring> // #150 - for strcmp
|
||||
|
||||
UIGridPoint::UIGridPoint()
|
||||
: walkable(false), transparent(false), grid_x(-1), grid_y(-1), parent_grid(nullptr)
|
||||
{}
|
||||
// #332 - cell storage moved to GridData's dense uint8 planes; UIGridPoint no
|
||||
// longer holds per-cell data. The Python GridPoint wrapper reaches walkable/
|
||||
// transparent through the grid's plane accessors keyed by (x, y).
|
||||
|
||||
// Utility function to convert sf::Color to PyObject*
|
||||
PyObject* sfColor_to_PyObject(sf::Color color) {
|
||||
|
|
@ -40,56 +40,46 @@ sf::Color PyObject_to_sfColor(PyObject* obj) {
|
|||
|
||||
// #150 - Removed get_color/set_color - now handled by layers
|
||||
|
||||
// Helper to safely get the GridPoint data from coordinates
|
||||
// Routes through UIGrid::at() which handles both flat and chunked storage
|
||||
static UIGridPoint* getGridPointData(PyUIGridPointObject* self) {
|
||||
if (!self->grid) return nullptr;
|
||||
if (self->x < 0 || self->x >= self->grid->grid_w ||
|
||||
self->y < 0 || self->y >= self->grid->grid_h) return nullptr;
|
||||
return &self->grid->at(self->x, self->y);
|
||||
// #332 - validate the wrapper's (grid, x, y) still name a live in-bounds cell.
|
||||
static bool gridPointValid(PyUIGridPointObject* self) {
|
||||
return self->grid &&
|
||||
self->x >= 0 && self->x < self->grid->grid_w &&
|
||||
self->y >= 0 && self->y < self->grid->grid_h;
|
||||
}
|
||||
|
||||
PyObject* UIGridPoint::get_bool_member(PyUIGridPointObject* self, void* closure) {
|
||||
auto* data = getGridPointData(self);
|
||||
if (!data) {
|
||||
if (!gridPointValid(self)) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "GridPoint data is no longer valid");
|
||||
return NULL;
|
||||
}
|
||||
if (reinterpret_cast<intptr_t>(closure) == 0) { // walkable
|
||||
return PyBool_FromLong(data->walkable);
|
||||
return PyBool_FromLong(self->grid->isWalkable(self->x, self->y));
|
||||
} else { // transparent
|
||||
return PyBool_FromLong(data->transparent);
|
||||
return PyBool_FromLong(self->grid->isTransparent(self->x, self->y));
|
||||
}
|
||||
}
|
||||
|
||||
int UIGridPoint::set_bool_member(PyUIGridPointObject* self, PyObject* value, void* closure) {
|
||||
auto* data = getGridPointData(self);
|
||||
if (!data) {
|
||||
if (!gridPointValid(self)) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "GridPoint data is no longer valid");
|
||||
return -1;
|
||||
}
|
||||
if (value == Py_True) {
|
||||
if (reinterpret_cast<intptr_t>(closure) == 0) { // walkable
|
||||
data->walkable = true;
|
||||
} else { // transparent
|
||||
data->transparent = true;
|
||||
}
|
||||
} else if (value == Py_False) {
|
||||
if (reinterpret_cast<intptr_t>(closure) == 0) { // walkable
|
||||
data->walkable = false;
|
||||
} else { // transparent
|
||||
data->transparent = false;
|
||||
}
|
||||
} else {
|
||||
bool bv;
|
||||
if (value == Py_True) bv = true;
|
||||
else if (value == Py_False) bv = false;
|
||||
else {
|
||||
PyErr_SetString(PyExc_ValueError, "Expected a boolean value");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Sync with TCOD map if parent grid exists
|
||||
if (data->parent_grid && data->grid_x >= 0 && data->grid_y >= 0) {
|
||||
data->parent_grid->syncTCODMapCell(data->grid_x, data->grid_y);
|
||||
if (reinterpret_cast<intptr_t>(closure) == 0) { // walkable
|
||||
self->grid->setWalkable(self->x, self->y, bv);
|
||||
} else { // transparent
|
||||
self->grid->setTransparent(self->x, self->y, bv);
|
||||
}
|
||||
|
||||
// Per-cell TCOD sync (unchanged behavior; #333 will make this lazy)
|
||||
self->grid->syncTCODMapCell(self->x, self->y);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -149,11 +139,10 @@ PyGetSetDef UIGridPoint::getsetters[] = {
|
|||
|
||||
PyObject* UIGridPoint::repr(PyUIGridPointObject* self) {
|
||||
std::ostringstream ss;
|
||||
auto* gp = getGridPointData(self);
|
||||
if (!gp) ss << "<GridPoint (invalid internal object)>";
|
||||
if (!gridPointValid(self)) ss << "<GridPoint (invalid internal object)>";
|
||||
else {
|
||||
ss << "<GridPoint (walkable=" << (gp->walkable ? "True" : "False")
|
||||
<< ", transparent=" << (gp->transparent ? "True" : "False")
|
||||
ss << "<GridPoint (walkable=" << (self->grid->isWalkable(self->x, self->y) ? "True" : "False")
|
||||
<< ", transparent=" << (self->grid->isTransparent(self->x, self->y) ? "True" : "False")
|
||||
<< ") at (" << self->x << ", " << self->y << ")>";
|
||||
}
|
||||
std::string repr_str = ss.str();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue