Grid code quality improvements

* Grid [x, y] subscript - convenience for `.at()`
* Extract UIEntityCollection - cleanup of UIGrid.cpp
* Thread-safe type cache - PyTypeCache
* Exception-safe extend() - validate before modify
This commit is contained in:
John McCardle 2026-01-10 08:37:31 -05:00
commit d6ef29f3cd
7 changed files with 1492 additions and 1097 deletions

View file

@ -23,6 +23,7 @@
#include "GridLayers.h"
#include "GridChunk.h"
#include "SpatialHash.h"
#include "UIEntityCollection.h" // EntityCollection types (extracted from UIGrid)
class UIGrid: public UIDrawable
{
@ -180,6 +181,8 @@ public:
static PyMethodDef methods[];
static PyGetSetDef getsetters[];
static PyMappingMethods mpmethods; // For grid[x, y] subscript access
static PyObject* subscript(PyUIGridObject* self, PyObject* key); // __getitem__
static PyObject* get_entities(PyUIGridObject* self, void* closure);
static PyObject* get_children(PyUIGridObject* self, void* closure);
static PyObject* repr(PyUIGridObject* self);
@ -200,54 +203,7 @@ public:
static PyObject* py_layer(PyUIGridObject* self, PyObject* args);
};
typedef struct {
PyObject_HEAD
std::shared_ptr<std::list<std::shared_ptr<UIEntity>>> data;
std::shared_ptr<UIGrid> grid;
} PyUIEntityCollectionObject;
class UIEntityCollection {
public:
static PySequenceMethods sqmethods;
static PyMappingMethods mpmethods;
static PyObject* append(PyUIEntityCollectionObject* self, PyObject* o);
static PyObject* extend(PyUIEntityCollectionObject* self, PyObject* o);
static PyObject* remove(PyUIEntityCollectionObject* self, PyObject* o);
static PyObject* pop(PyUIEntityCollectionObject* self, PyObject* args);
static PyObject* insert(PyUIEntityCollectionObject* self, PyObject* args);
static PyObject* index_method(PyUIEntityCollectionObject* self, PyObject* value);
static PyObject* count(PyUIEntityCollectionObject* self, PyObject* value);
static PyObject* find(PyUIEntityCollectionObject* self, PyObject* args, PyObject* kwds);
static PyMethodDef methods[];
static PyObject* repr(PyUIEntityCollectionObject* self);
static int init(PyUIEntityCollectionObject* self, PyObject* args, PyObject* kwds);
static PyObject* iter(PyUIEntityCollectionObject* self);
static Py_ssize_t len(PyUIEntityCollectionObject* self);
static PyObject* getitem(PyUIEntityCollectionObject* self, Py_ssize_t index);
static int setitem(PyUIEntityCollectionObject* self, Py_ssize_t index, PyObject* value);
static int contains(PyUIEntityCollectionObject* self, PyObject* value);
static PyObject* concat(PyUIEntityCollectionObject* self, PyObject* other);
static PyObject* inplace_concat(PyUIEntityCollectionObject* self, PyObject* other);
static PyObject* subscript(PyUIEntityCollectionObject* self, PyObject* key);
static int ass_subscript(PyUIEntityCollectionObject* self, PyObject* key, PyObject* value);
};
typedef struct {
PyObject_HEAD
std::shared_ptr<std::list<std::shared_ptr<UIEntity>>> data;
std::list<std::shared_ptr<UIEntity>>::iterator current; // Actual list iterator - O(1) increment
std::list<std::shared_ptr<UIEntity>>::iterator end; // End iterator for bounds check
int start_size; // For detecting modification during iteration
} PyUIEntityCollectionIterObject;
class UIEntityCollectionIter {
public:
static int init(PyUIEntityCollectionIterObject* self, PyObject* args, PyObject* kwds);
static PyObject* next(PyUIEntityCollectionIterObject* self);
static PyObject* repr(PyUIEntityCollectionIterObject* self);
static PyObject* getitem(PyUIEntityCollectionObject* self, Py_ssize_t index);
};
// UIEntityCollection types are now in UIEntityCollection.h
// Forward declaration of methods array
extern PyMethodDef UIGrid_all_methods[];
@ -268,11 +224,8 @@ namespace mcrfpydef {
obj->data.reset();
Py_TYPE(self)->tp_free(self);
},
//TODO - PyUIGrid REPR def:
.tp_repr = (reprfunc)UIGrid::repr,
//.tp_hash = NULL,
//.tp_iter
//.tp_iternext
.tp_as_mapping = &UIGrid::mpmethods, // Enable grid[x, y] subscript access
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.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"
@ -330,61 +283,6 @@ namespace mcrfpydef {
}
};
// #189 - Use inline instead of static to ensure single instance across translation units
inline PyTypeObject PyUIEntityCollectionIterType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.UIEntityCollectionIter",
.tp_basicsize = sizeof(PyUIEntityCollectionIterObject),
.tp_itemsize = 0,
.tp_dealloc = (destructor)[](PyObject* self)
{
PyUIEntityCollectionIterObject* obj = (PyUIEntityCollectionIterObject*)self;
obj->data.reset();
Py_TYPE(self)->tp_free(self);
},
.tp_repr = (reprfunc)UIEntityCollectionIter::repr,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("Iterator for a collection of UI objects"),
.tp_iter = PyObject_SelfIter,
.tp_iternext = (iternextfunc)UIEntityCollectionIter::next,
//.tp_getset = UIEntityCollection::getset,
.tp_init = (initproc)UIEntityCollectionIter::init, // just raise an exception
.tp_alloc = PyType_GenericAlloc,
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
{
PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required.");
return NULL;
}
};
// #189 - Use inline instead of static to ensure single instance across translation units
inline PyTypeObject PyUIEntityCollectionType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.EntityCollection",
.tp_basicsize = sizeof(PyUIEntityCollectionObject),
.tp_itemsize = 0,
.tp_dealloc = (destructor)[](PyObject* self)
{
PyUIEntityCollectionObject* obj = (PyUIEntityCollectionObject*)self;
obj->data.reset();
Py_TYPE(self)->tp_free(self);
},
.tp_repr = (reprfunc)UIEntityCollection::repr,
.tp_as_sequence = &UIEntityCollection::sqmethods,
.tp_as_mapping = &UIEntityCollection::mpmethods,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("Iterable, indexable collection of Entities"),
.tp_iter = (getiterfunc)UIEntityCollection::iter,
.tp_methods = UIEntityCollection::methods, // append, remove
//.tp_getset = UIEntityCollection::getset,
.tp_init = (initproc)UIEntityCollection::init, // just raise an exception
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
{
// Does PyUIEntityCollectionType need __new__ if it's not supposed to be instantiable by the user?
// Should I just raise an exception? Or is the uninitialized shared_ptr enough of a blocker?
PyErr_SetString(PyExc_TypeError, "EntityCollection cannot be instantiated: a C++ data source is required.");
return NULL;
}
};
// EntityCollection types moved to UIEntityCollection.h
}