From 9efe998a3396182b468969b9a495164bd95100a3 Mon Sep 17 00:00:00 2001 From: John McCardle Date: Sat, 13 Apr 2024 00:17:43 -0400 Subject: [PATCH] some work on UICaption and UICollection; fixing segfaults resulting from mcrfpydef namepace TypeObject usage --- src/UICaption.cpp | 4 +- src/UICollection.cpp | 201 +++++++++++++++++++++++++++++++++++++++++++ src/UICollection.h | 77 ++++++++++------- src/UIDrawable.h | 7 ++ 4 files changed, 257 insertions(+), 32 deletions(-) diff --git a/src/UICaption.cpp b/src/UICaption.cpp index 9d8c21d..21c9c36 100644 --- a/src/UICaption.cpp +++ b/src/UICaption.cpp @@ -115,7 +115,7 @@ int UICaption::set_color_member(PyUICaptionObject* self, PyObject* value, void* auto member_ptr = reinterpret_cast(closure); //TODO: this logic of (PyColor instance OR tuple -> sf::color) should be encapsulated for reuse int r, g, b, a; - if (PyObject_IsInstance(value, (PyObject*)&mcrfpydef::PyColorType)) + if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color") /*(PyObject*)&mcrfpydef::PyColorType)*/)) { // get value from mcrfpy.Color instance auto c = ((PyColorObject*)value)->data; @@ -236,7 +236,7 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds) // check types for font, fill_color, outline_color std::cout << PyUnicode_AsUTF8(PyObject_Repr(font)) << std::endl; - if (font != NULL && !PyObject_IsInstance(font, (PyObject*)&PyFontType)){ + if (font != NULL && !PyObject_IsInstance(font, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Font")/*(PyObject*)&PyFontType)*/)){ PyErr_SetString(PyExc_TypeError, "font must be a mcrfpy.Font instance"); return -1; } else if (font != NULL) diff --git a/src/UICollection.cpp b/src/UICollection.cpp index 8133952..7aa16cb 100644 --- a/src/UICollection.cpp +++ b/src/UICollection.cpp @@ -1,2 +1,203 @@ #include "UICollection.h" +#include "UIFrame.h" +#include "UICaption.h" +#include "UISprite.h" +#include "UIGrid.h" +#include "McRFPy_API.h" + +using namespace mcrfpydef; + +int UICollectionIter::init(PyUICollectionIterObject* self, PyObject* args, PyObject* kwds) +{ + PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required."); + return -1; +} + +PyObject* UICollectionIter::next(PyUICollectionIterObject* self) +{ + if (self->data->size() != self->start_size) + { + PyErr_SetString(PyExc_RuntimeError, "collection changed size during iteration"); + return NULL; + } + + if (self->index > self->start_size - 1) + { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + self->index++; + auto vec = self->data.get(); + if (!vec) + { + PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer"); + return NULL; + } + auto target = (*vec)[self->index-1]; + // TODO build PyObject* of the correct UIDrawable subclass to return + //return py_instance(target); + return NULL; +} + +PyObject* UICollectionIter::repr(PyUICollectionIterObject* self) +{ + std::ostringstream ss; + if (!self->data) ss << ""; + else { + ss << "data->size() << " child objects, @ index " << self->index << ")>"; + } + std::string repr_str = ss.str(); + return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); +} + +Py_ssize_t UICollection::len(PyUICollectionObject* self) { + return self->data->size(); +} + +PyObject* UICollection::getitem(PyUICollectionObject* self, Py_ssize_t index) { + // build a Python version of item at self->data[index] + // Copy pasted:: + auto vec = self->data.get(); + if (!vec) + { + PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer"); + return NULL; + } + while (index < 0) index += self->data->size(); + if (index > self->data->size() - 1) + { + PyErr_SetString(PyExc_IndexError, "UICollection index out of range"); + return NULL; + } + auto target = (*vec)[index]; + RET_PY_INSTANCE(target); +return NULL; + + +} + +PySequenceMethods UICollection::sqmethods = { + .sq_length = (lenfunc)UICollection::len, + .sq_item = (ssizeargfunc)UICollection::getitem, + //.sq_item_by_index = PyUICollection_getitem + //.sq_slice - return a subset of the iterable + //.sq_ass_item - called when `o[x] = y` is executed (x is any object type) + //.sq_ass_slice - cool; no thanks, for now + //.sq_contains - called when `x in o` is executed + //.sq_ass_item_by_index - called when `o[x] = y` is executed (x is explictly an integer) +}; + +/* Idiomatic way to fetch complete types from the API rather than referencing their PyTypeObject struct + +auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"); + +I never identified why `using namespace mcrfpydef;` doesn't solve the segfault issue. +The horrible macro in UIDrawable was originally a workaround for this, but as I interact with the types outside of the monster UI.h, a more general (and less icky) solution is required. + +*/ + +PyObject* UICollection::append(PyUICollectionObject* self, PyObject* o) +{ + // if not UIDrawable subclass, reject it + // self->data->push_back( c++ object inside o ); + + // this would be a great use case for .tp_base + if (!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) && + !PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) && + !PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) && + !PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid")) + ) + { + PyErr_SetString(PyExc_TypeError, "Only Frame, Caption, Sprite, and Grid objects can be added to UICollection"); + return NULL; + } + + if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) + { + PyUIFrameObject* frame = (PyUIFrameObject*)o; + self->data->push_back(frame->data); + } + if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) + { + PyUICaptionObject* caption = (PyUICaptionObject*)o; + self->data->push_back(caption->data); + } + if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) + { + PyUISpriteObject* sprite = (PyUISpriteObject*)o; + self->data->push_back(sprite->data); + } + if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) + { + PyUIGridObject* grid = (PyUIGridObject*)o; + self->data->push_back(grid->data); + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* UICollection::remove(PyUICollectionObject* self, PyObject* o) +{ + if (!PyLong_Check(o)) + { + PyErr_SetString(PyExc_TypeError, "UICollection.remove requires an integer index to remove"); + return NULL; + } + long index = PyLong_AsLong(o); + if (index >= self->data->size()) + { + PyErr_SetString(PyExc_ValueError, "Index out of range"); + return NULL; + } + else if (index < 0) + { + PyErr_SetString(PyExc_NotImplementedError, "reverse indexing is not implemented."); + return NULL; + } + + // release the shared pointer at self->data[index]; + self->data->erase(self->data->begin() + index); + Py_INCREF(Py_None); + return Py_None; +} + +PyMethodDef UICollection::methods[] = { + {"append", (PyCFunction)UICollection::append, METH_O}, + //{"extend", (PyCFunction)PyUICollection_extend, METH_O}, // TODO + {"remove", (PyCFunction)UICollection::remove, METH_O}, + {NULL, NULL, 0, NULL} +}; + +PyObject* UICollection::repr(PyUICollectionObject* self) +{ + std::ostringstream ss; + if (!self->data) ss << ""; + else { + ss << "data->size() << " child objects)>"; + } + std::string repr_str = ss.str(); + return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); +} + +int UICollection::init(PyUICollectionObject* self, PyObject* args, PyObject* kwds) +{ + PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required."); + return -1; +} + +PyObject* UICollection::iter(PyUICollectionObject* self) +{ + PyUICollectionIterObject* iterObj; + iterObj = (PyUICollectionIterObject*)PyUICollectionIterType.tp_alloc(&PyUICollectionIterType, 0); + if (iterObj == NULL) { + return NULL; // Failed to allocate memory for the iterator object + } + + iterObj->data = self->data; + iterObj->index = 0; + iterObj->start_size = self->data->size(); + + return (PyObject*)iterObj; +} diff --git a/src/UICollection.h b/src/UICollection.h index 03136f0..9b6f6da 100644 --- a/src/UICollection.h +++ b/src/UICollection.h @@ -1,40 +1,47 @@ #pragma once #include "Common.h" #include "Python.h" -#include "structmember.h" -#include "IndexTexture.h" -#include "Resources.h" -#include - -#include "PyCallable.h" -#include "PyTexture.h" -#include "PyColor.h" -#include "PyVector.h" -#include "PyFont.h" #include "UIDrawable.h" -#include "UICaption.h" -#include "UIFrame.h" -#include "UISprite.h" -#include "UIGrid.h" + +class UICollectionIter +{ + // really more of a namespace: all the members are public and static. But being consistent with other UI objects +public: + static int init(PyUICollectionIterObject* self, PyObject* args, PyObject* kwds); + static PyObject* next(PyUICollectionIterObject* self); + static PyObject* repr(PyUICollectionIterObject* self); +}; + +class UICollection +{ + // asdf +public: + static Py_ssize_t len(PyUICollectionObject* self); + static PyObject* getitem(PyUICollectionObject* self, Py_ssize_t index); + static PySequenceMethods sqmethods; + static PyObject* append(PyUICollectionObject* self, PyObject* o); + static PyObject* remove(PyUICollectionObject* self, PyObject* o); + static PyMethodDef methods[]; + static PyObject* repr(PyUICollectionObject* self); + static int init(PyUICollectionObject* self, PyObject* args, PyObject* kwds); + static PyObject* iter(PyUICollectionObject* self); +}; namespace mcrfpydef { - typedef struct { - PyObject_HEAD - std::shared_ptr>> data; - int index; - int start_size; - } PyUICollectionIterObject; + //TODO: add this method to class scope; move implementation to .cpp file +/* static int PyUICollectionIter_init(PyUICollectionIterObject* self, PyObject* args, PyObject* kwds) { PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required."); return -1; } - +*/ //TODO: add this method to class scope; move implementation to .cpp file +/* static PyObject* PyUICollectionIter_next(PyUICollectionIterObject* self) { if (self->data->size() != self->start_size) @@ -60,8 +67,10 @@ namespace mcrfpydef { //return py_instance(target); return NULL; } +*/ //TODO: add this method to class scope; move implementation to .cpp file +/* static PyObject* PyUICollectionIter_repr(PyUICollectionIterObject* self) { std::ostringstream ss; @@ -72,31 +81,34 @@ namespace mcrfpydef { std::string repr_str = ss.str(); return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); } +*/ static PyTypeObject PyUICollectionIterType = { //PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "mcrfpy.UICollectionIter", .tp_basicsize = sizeof(PyUICollectionIterObject), .tp_itemsize = 0, + //TODO - as static method, not inline lambda def, please .tp_dealloc = (destructor)[](PyObject* self) { PyUICollectionIterObject* obj = (PyUICollectionIterObject*)self; obj->data.reset(); Py_TYPE(self)->tp_free(self); }, - .tp_repr = (reprfunc)PyUICollectionIter_repr, + .tp_repr = (reprfunc)UICollectionIter::repr, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = PyDoc_STR("Iterator for a collection of UI objects"), - .tp_iternext = (iternextfunc)PyUICollectionIter_next, + .tp_iternext = (iternextfunc)UICollectionIter::next, //.tp_getset = PyUICollection_getset, - .tp_init = (initproc)PyUICollectionIter_init, // just raise an exception + .tp_init = (initproc)UICollectionIter::init, // just raise an exception + //TODO - as static method, not inline lambda def, please .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; } }; - +/* //TODO: add this method to class scope; move implementation to .cpp file static Py_ssize_t PyUICollection_len(PyUICollectionObject* self) { return self->data->size(); @@ -206,6 +218,7 @@ namespace mcrfpydef { } //TODO: add this static array to class scope; move implementation to .cpp file + static PyMethodDef PyUICollection_methods[] = { {"append", (PyCFunction)PyUICollection_append, METH_O}, //{"extend", (PyCFunction)PyUICollection_extend, METH_O}, // TODO @@ -248,25 +261,29 @@ namespace mcrfpydef { return (PyObject*)iterObj; } +*/ + static PyTypeObject PyUICollectionType = { //PyVarObject_/HEAD_INIT(NULL, 0) .tp_name = "mcrfpy.UICollection", .tp_basicsize = sizeof(PyUICollectionObject), .tp_itemsize = 0, + //TODO - as static method, not inline lambda def, please .tp_dealloc = (destructor)[](PyObject* self) { PyUICollectionObject* obj = (PyUICollectionObject*)self; obj->data.reset(); Py_TYPE(self)->tp_free(self); }, - .tp_repr = (reprfunc)PyUICollection_repr, - .tp_as_sequence = &PyUICollection_sqmethods, + .tp_repr = (reprfunc)UICollection::repr, + .tp_as_sequence = &UICollection::sqmethods, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = PyDoc_STR("Iterable, indexable collection of UI objects"), - .tp_iter = (getiterfunc)PyUICollection_iter, - .tp_methods = PyUICollection_methods, // append, remove + .tp_iter = (getiterfunc)UICollection::iter, + .tp_methods = UICollection::methods, // append, remove //.tp_getset = PyUICollection_getset, - .tp_init = (initproc)PyUICollection_init, // just raise an exception + .tp_init = (initproc)UICollection::init, // just raise an exception + //TODO - as static method, not inline lambda def, please .tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject* { // Does PyUICollectionType need __new__ if it's not supposed to be instantiable by the user? diff --git a/src/UIDrawable.h b/src/UIDrawable.h index d1a6b6e..4e636c5 100644 --- a/src/UIDrawable.h +++ b/src/UIDrawable.h @@ -48,6 +48,13 @@ typedef struct { std::shared_ptr>> data; } PyUICollectionObject; +typedef struct { + PyObject_HEAD + std::shared_ptr>> data; + int index; + int start_size; +} PyUICollectionIterObject; + namespace mcrfpydef { //PyObject* py_instance(std::shared_ptr source); // This function segfaults on tp_alloc for an unknown reason, but works inline with mcrfpydef:: methods.