Replace UIEntity gridstate with DiscreteMap perspective_map; closes #294
Per-entity FOV memory moves from std::vector<UIGridPointState> (two-bool visible/discovered pairs) to a 3-state DiscreteMap (0=UNKNOWN, 1=DISCOVERED, 2=VISIBLE), exposed as entity.perspective_map. The invariant visible-subset-of-discovered becomes structural (single value per cell), and the map is a live, serializable, first-class object rather than an implicit internal array. Changes: - New DiscreteMap C++ class with shared ownership; PyDiscreteMapObject now holds shared_ptr<DiscreteMap>. UIEntity holds the same shared_ptr. - New mcrfpy.Perspective IntEnum (UNKNOWN/DISCOVERED/VISIBLE), modelled on PyInputState. - entity.perspective_map: lazy-allocated on first access with a grid; setter validates size against grid and raises ValueError on mismatch; None clears (next access lazy-reallocates fresh). - updateVisibility() now demotes 2->1 then promotes visible cells to 2. - entity.at(x, y) returns grid.at(x, y) when VISIBLE, else None. - Fog-of-war rendering in UIGridView and UIGrid reads the 3-state map. - Removed: UIEntity::gridstate, ensureGridstate(), entity.gridstate getter, UIGridPointState struct + PyUIGridPointStateType. - Obsolete tests deleted (test_gridpointstate_point, issue_265_gridpointstate_dangle); 4 new tests cover lazy allocation, identity, serialization round-trip, size validation, and the visible-subset-of-discovered invariant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
417fc43325
commit
f797120d53
24 changed files with 7998 additions and 1906 deletions
|
|
@ -157,94 +157,8 @@ PyObject* UIGridPoint::repr(PyUIGridPointObject* self) {
|
|||
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
|
||||
}
|
||||
|
||||
// Helper to safely get the GridPointState data from coordinates
|
||||
static UIGridPointState* getGridPointStateData(PyUIGridPointStateObject* self) {
|
||||
if (!self->entity || !self->entity->grid) return nullptr;
|
||||
int idx = self->y * self->entity->grid->grid_w + self->x;
|
||||
if (idx < 0 || idx >= static_cast<int>(self->entity->gridstate.size())) return nullptr;
|
||||
return &self->entity->gridstate[idx];
|
||||
}
|
||||
|
||||
PyObject* UIGridPointState::get_bool_member(PyUIGridPointStateObject* self, void* closure) {
|
||||
auto* data = getGridPointStateData(self);
|
||||
if (!data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "GridPointState data is no longer valid");
|
||||
return NULL;
|
||||
}
|
||||
if (reinterpret_cast<intptr_t>(closure) == 0) { // visible
|
||||
return PyBool_FromLong(data->visible);
|
||||
} else { // discovered
|
||||
return PyBool_FromLong(data->discovered);
|
||||
}
|
||||
}
|
||||
|
||||
int UIGridPointState::set_bool_member(PyUIGridPointStateObject* self, PyObject* value, void* closure) {
|
||||
auto* data = getGridPointStateData(self);
|
||||
if (!data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "GridPointState data is no longer valid");
|
||||
return -1;
|
||||
}
|
||||
if (!PyBool_Check(value)) {
|
||||
PyErr_SetString(PyExc_TypeError, "Value must be a boolean");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int truthValue = PyObject_IsTrue(value);
|
||||
if (truthValue < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (reinterpret_cast<intptr_t>(closure) == 0) { // visible
|
||||
data->visible = truthValue;
|
||||
} else { // discovered
|
||||
data->discovered = truthValue;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// #16 - Get GridPoint at this position (None if not discovered)
|
||||
PyObject* UIGridPointState::get_point(PyUIGridPointStateObject* self, void* closure) {
|
||||
// Return None if entity hasn't discovered this cell
|
||||
auto* data = getGridPointStateData(self);
|
||||
if (!data || !data->discovered) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
if (!self->grid) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "GridPointState has no parent grid");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Return the GridPoint at this position (use type directly since it's internal-only)
|
||||
auto type = &mcrfpydef::PyUIGridPointType;
|
||||
auto obj = (PyUIGridPointObject*)type->tp_alloc(type, 0);
|
||||
if (!obj) return NULL;
|
||||
|
||||
obj->grid = self->grid;
|
||||
obj->x = self->x;
|
||||
obj->y = self->y;
|
||||
return (PyObject*)obj;
|
||||
}
|
||||
|
||||
PyGetSetDef UIGridPointState::getsetters[] = {
|
||||
{"visible", (getter)UIGridPointState::get_bool_member, (setter)UIGridPointState::set_bool_member, "Is the GridPointState visible", (void*)0},
|
||||
{"discovered", (getter)UIGridPointState::get_bool_member, (setter)UIGridPointState::set_bool_member, "Has the GridPointState been discovered", (void*)1},
|
||||
{"point", (getter)UIGridPointState::get_point, NULL, "GridPoint at this position (None if not discovered)", NULL},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
PyObject* UIGridPointState::repr(PyUIGridPointStateObject* self) {
|
||||
std::ostringstream ss;
|
||||
auto* gps = getGridPointStateData(self);
|
||||
if (!gps) ss << "<GridPointState (invalid internal object)>";
|
||||
else {
|
||||
ss << "<GridPointState (visible=" << (gps->visible ? "True" : "False") << ", discovered=" << (gps->discovered ? "True" : "False") <<
|
||||
")>";
|
||||
}
|
||||
std::string repr_str = ss.str();
|
||||
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
|
||||
}
|
||||
// UIGridPointState removed in #294. Per-entity visibility memory now lives on
|
||||
// UIEntity::perspective_map as a DiscreteMap. See mcrfpy.Perspective enum.
|
||||
|
||||
// #150 - Dynamic attribute access for named layers
|
||||
PyObject* UIGridPoint::getattro(PyUIGridPointObject* self, PyObject* name) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue