Migrate static PyTypeObject to inline, delete PyTypeCache workarounds

All 27 PyTypeObject declarations in namespace mcrfpydef headers changed
from `static` to `inline` (C++17), ensuring a single global instance
across translation units. This fixes the root cause of stale-type-pointer
segfaults where only the McRFPy_API.cpp copy was PyType_Ready'd.

Replaced ~20 PyTypeCache call sites and 2 PyRAII::PyTypeRef lookups with
direct &mcrfpydef::Type references. Deleted PyTypeCache.h/.cpp,
PyObjectUtils.h, and PyRAII.h (all were workarounds for the static bug).

228/228 tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
John McCardle 2026-02-16 20:58:09 -05:00
commit bb72040396
37 changed files with 74 additions and 600 deletions

View file

@ -5,7 +5,6 @@
#include "MeshLayer.h" // For MeshVertex
#include "../platform/GLContext.h"
#include "../PyTexture.h"
#include "../PyTypeCache.h"
#include <cmath>
#include <iostream>
@ -355,8 +354,8 @@ int Billboard::init(PyObject* self, PyObject* args, PyObject* kwds) {
// Handle texture
if (textureObj && textureObj != Py_None) {
PyTypeObject* textureType = PyTypeCache::Texture();
if (textureType && PyObject_IsInstance(textureObj, (PyObject*)textureType)) {
PyTypeObject* textureType = &mcrfpydef::PyTextureType;
if (PyObject_IsInstance(textureObj, (PyObject*)textureType)) {
PyTextureObject* texPy = (PyTextureObject*)textureObj;
if (texPy->data) {
selfObj->data->setTexture(texPy->data);
@ -443,12 +442,7 @@ int Billboard::set_texture(PyObject* self, PyObject* value, void* closure) {
obj->data->setTexture(nullptr);
return 0;
}
// Use PyTypeCache to get properly initialized type object
PyTypeObject* textureType = PyTypeCache::Texture();
if (!textureType) {
PyErr_SetString(PyExc_RuntimeError, "Texture type not initialized");
return -1;
}
PyTypeObject* textureType = &mcrfpydef::PyTextureType;
if (PyObject_IsInstance(value, (PyObject*)textureType)) {
PyTextureObject* texPy = (PyTextureObject*)value;
if (texPy->data) {

View file

@ -246,7 +246,7 @@ public:
namespace mcrfpydef {
// ColorLayer type
static PyTypeObject PyColorLayerType = {
inline PyTypeObject PyColorLayerType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.ColorLayer",
.tp_basicsize = sizeof(PyColorLayerObject),
@ -299,7 +299,7 @@ namespace mcrfpydef {
};
// TileLayer type
static PyTypeObject PyTileLayerType = {
inline PyTypeObject PyTileLayerType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.TileLayer",
.tp_basicsize = sizeof(PyTileLayerObject),

View file

@ -3,7 +3,6 @@
#include "McRFPy_Automation.h"
// Note: McRFPy_Libtcod.h removed in #215 - functionality moved to mcrfpy.bresenham()
#include "McRFPy_Doc.h"
#include "PyTypeCache.h" // Thread-safe cached Python types
#include "platform.h"
#include "PyAnimation.h"
#include "PyDrawable.h"
@ -756,14 +755,6 @@ PyObject* PyInit_mcrfpy()
// - line() functionality replaced by mcrfpy.bresenham()
// - compute_fov() redundant with Grid.compute_fov()
// Initialize PyTypeCache for thread-safe type lookups
// This must be done after all types are added to the module
if (!PyTypeCache::initialize(m)) {
// Failed to initialize type cache - this is a critical error
// Error message already set by PyTypeCache::initialize
return NULL;
}
//McRFPy_API::mcrf_module = m;
return m;
}

View file

@ -38,7 +38,7 @@ public:
};
namespace mcrfpydef {
static PyTypeObject PyAnimationType = {
inline PyTypeObject PyAnimationType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Animation",
.tp_basicsize = sizeof(PyAnimationObject),

View file

@ -1,7 +1,5 @@
#include "PyColor.h"
#include "McRFPy_API.h"
#include "PyObjectUtils.h"
#include "PyRAII.h"
#include "McRFPy_Doc.h"
#include <string>
#include <cstdio>
@ -288,33 +286,28 @@ int PyColor::set_member(PyObject* obj, PyObject* value, void* closure)
PyColorObject* PyColor::from_arg(PyObject* args)
{
// Use RAII for type reference management
PyRAII::PyTypeRef type("Color", McRFPy_API::mcrf_module);
if (!type) {
return NULL;
}
PyTypeObject* type = &mcrfpydef::PyColorType;
// Check if args is already a Color instance
if (PyObject_IsInstance(args, (PyObject*)type.get())) {
if (PyObject_IsInstance(args, (PyObject*)type)) {
Py_INCREF(args); // Return new reference so caller can safely DECREF
return (PyColorObject*)args;
}
// Create new Color object using RAII
PyRAII::PyObjectRef obj(type->tp_alloc(type.get(), 0), true);
// Create new Color object
PyObject* obj = type->tp_alloc(type, 0);
if (!obj) {
return NULL;
}
// Initialize the object
int err = init((PyColorObject*)obj.get(), args, NULL);
int err = init((PyColorObject*)obj, args, NULL);
if (err) {
// obj will be automatically cleaned up when it goes out of scope
Py_DECREF(obj);
return NULL;
}
// Release ownership and return
return (PyColorObject*)obj.release();
return (PyColorObject*)obj;
}
// Color helper method implementations

View file

@ -39,7 +39,7 @@ public:
};
namespace mcrfpydef {
static PyTypeObject PyColorType = {
inline PyTypeObject PyColorType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Color",
.tp_basicsize = sizeof(PyColorObject),

View file

@ -77,7 +77,7 @@ public:
};
namespace mcrfpydef {
static PyTypeObject PyDiscreteMapType = {
inline PyTypeObject PyDiscreteMapType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.DiscreteMap",
.tp_basicsize = sizeof(PyDiscreteMapObject),

View file

@ -30,7 +30,7 @@ public:
};
namespace mcrfpydef {
static PyTypeObject PyFontType = {
inline PyTypeObject PyFontType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Font",
.tp_basicsize = sizeof(PyFontObject),

View file

@ -88,7 +88,7 @@ public:
};
namespace mcrfpydef {
static PyTypeObject PyHeightMapType = {
inline PyTypeObject PyHeightMapType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.HeightMap",
.tp_basicsize = sizeof(PyHeightMapObject),

View file

@ -21,7 +21,7 @@ public:
};
namespace mcrfpydef {
static PyTypeObject PyKeyboardType = {
inline PyTypeObject PyKeyboardType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Keyboard",
.tp_basicsize = sizeof(PyKeyboardObject),

View file

@ -35,7 +35,7 @@ public:
};
namespace mcrfpydef {
static PyTypeObject PyMouseType = {
inline PyTypeObject PyMouseType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Mouse",
.tp_basicsize = sizeof(PyMouseObject),

View file

@ -62,7 +62,7 @@ public:
};
namespace mcrfpydef {
static PyTypeObject PyMusicType = {
inline PyTypeObject PyMusicType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Music",
.tp_basicsize = sizeof(PyMusicObject),

View file

@ -1,76 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include "McRFPy_API.h"
#include "PyRAII.h"
namespace PyObjectUtils {
// Template for getting Python type object from module
template<typename T>
PyTypeObject* getPythonType(const char* typeName) {
PyTypeObject* type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, typeName);
if (!type) {
PyErr_Format(PyExc_RuntimeError, "Could not find %s type in module", typeName);
}
return type;
}
// Generic function to create a Python object of given type
inline PyObject* createPyObjectGeneric(const char* typeName) {
PyTypeObject* type = getPythonType<void>(typeName);
if (!type) return nullptr;
PyObject* obj = type->tp_alloc(type, 0);
Py_DECREF(type);
return obj;
}
// Helper function to allocate and initialize a Python object with data
template<typename PyObjType, typename DataType>
PyObject* createPyObjectWithData(const char* typeName, DataType data) {
PyTypeObject* type = getPythonType<void>(typeName);
if (!type) return nullptr;
PyObjType* obj = (PyObjType*)type->tp_alloc(type, 0);
Py_DECREF(type);
if (obj) {
obj->data = data;
}
return (PyObject*)obj;
}
// Function to convert UIDrawable to appropriate Python object
// This is moved to UICollection.cpp to avoid circular dependencies
// RAII-based object creation example
inline PyObject* createPyObjectGenericRAII(const char* typeName) {
PyRAII::PyTypeRef type(typeName, McRFPy_API::mcrf_module);
if (!type) {
PyErr_Format(PyExc_RuntimeError, "Could not find %s type in module", typeName);
return nullptr;
}
PyObject* obj = type->tp_alloc(type.get(), 0);
// Return the new reference (caller owns it)
return obj;
}
// Example of using PyObjectRef for safer reference management
template<typename PyObjType, typename DataType>
PyObject* createPyObjectWithDataRAII(const char* typeName, DataType data) {
PyRAII::PyObjectRef obj = PyRAII::createObject<PyObjType>(typeName, McRFPy_API::mcrf_module);
if (!obj) {
PyErr_Format(PyExc_RuntimeError, "Could not create %s object", typeName);
return nullptr;
}
// Access the object through the RAII wrapper
((PyObjType*)obj.get())->data = data;
// Release ownership to return to Python
return obj.release();
}
}

View file

@ -1,138 +0,0 @@
#pragma once
#include "Python.h"
#include <utility>
namespace PyRAII {
// RAII wrapper for PyObject* that automatically manages reference counting
class PyObjectRef {
private:
PyObject* ptr;
public:
// Constructors
PyObjectRef() : ptr(nullptr) {}
explicit PyObjectRef(PyObject* p, bool steal_ref = false) : ptr(p) {
if (ptr && !steal_ref) {
Py_INCREF(ptr);
}
}
// Copy constructor
PyObjectRef(const PyObjectRef& other) : ptr(other.ptr) {
if (ptr) {
Py_INCREF(ptr);
}
}
// Move constructor
PyObjectRef(PyObjectRef&& other) noexcept : ptr(other.ptr) {
other.ptr = nullptr;
}
// Destructor
~PyObjectRef() {
Py_XDECREF(ptr);
}
// Copy assignment
PyObjectRef& operator=(const PyObjectRef& other) {
if (this != &other) {
Py_XDECREF(ptr);
ptr = other.ptr;
if (ptr) {
Py_INCREF(ptr);
}
}
return *this;
}
// Move assignment
PyObjectRef& operator=(PyObjectRef&& other) noexcept {
if (this != &other) {
Py_XDECREF(ptr);
ptr = other.ptr;
other.ptr = nullptr;
}
return *this;
}
// Access operators
PyObject* get() const { return ptr; }
PyObject* operator->() const { return ptr; }
PyObject& operator*() const { return *ptr; }
operator bool() const { return ptr != nullptr; }
// Release ownership (for returning to Python)
PyObject* release() {
PyObject* temp = ptr;
ptr = nullptr;
return temp;
}
// Reset with new pointer
void reset(PyObject* p = nullptr, bool steal_ref = false) {
if (p != ptr) {
Py_XDECREF(ptr);
ptr = p;
if (ptr && !steal_ref) {
Py_INCREF(ptr);
}
}
}
};
// Helper class for managing PyTypeObject* references from module lookups
class PyTypeRef {
private:
PyTypeObject* type;
public:
PyTypeRef() : type(nullptr) {}
explicit PyTypeRef(const char* typeName, PyObject* module) {
type = (PyTypeObject*)PyObject_GetAttrString(module, typeName);
// GetAttrString returns a new reference, so we own it
}
~PyTypeRef() {
Py_XDECREF((PyObject*)type);
}
// Delete copy operations to prevent accidental reference issues
PyTypeRef(const PyTypeRef&) = delete;
PyTypeRef& operator=(const PyTypeRef&) = delete;
// Allow move operations
PyTypeRef(PyTypeRef&& other) noexcept : type(other.type) {
other.type = nullptr;
}
PyTypeRef& operator=(PyTypeRef&& other) noexcept {
if (this != &other) {
Py_XDECREF((PyObject*)type);
type = other.type;
other.type = nullptr;
}
return *this;
}
PyTypeObject* get() const { return type; }
PyTypeObject* operator->() const { return type; }
operator bool() const { return type != nullptr; }
};
// Convenience function to create a new object with RAII
template<typename PyObjType>
PyObjectRef createObject(const char* typeName, PyObject* module) {
PyTypeRef type(typeName, module);
if (!type) {
return PyObjectRef();
}
PyObject* obj = type->tp_alloc(type.get(), 0);
// tp_alloc returns a new reference, so we steal it
return PyObjectRef(obj, true);
}
}

View file

@ -47,7 +47,7 @@ public:
};
namespace mcrfpydef {
static PyTypeObject PySceneType = {
inline PyTypeObject PySceneType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Scene",
.tp_basicsize = sizeof(PySceneObject),

View file

@ -59,7 +59,7 @@ public:
};
namespace mcrfpydef {
static PyTypeObject PySoundType = {
inline PyTypeObject PySoundType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Sound",
.tp_basicsize = sizeof(PySoundObject),

View file

@ -1,7 +1,6 @@
#include "PyTexture.h"
#include "McRFPy_API.h"
#include "McRFPy_Doc.h"
#include "PyTypeCache.h"
#include <cmath>
#include <algorithm>
@ -93,13 +92,8 @@ sf::Sprite PyTexture::sprite(int index, sf::Vector2f pos, sf::Vector2f s)
PyObject* PyTexture::pyObject()
{
PyTypeObject* type = PyTypeCache::Texture();
if (!type) {
PyErr_SetString(PyExc_RuntimeError, "Failed to get Texture type from cache");
return NULL;
}
PyTypeObject* type = &mcrfpydef::PyTextureType;
PyObject* obj = PyTexture::pynew(type, Py_None, Py_None);
// PyTypeCache returns borrowed reference — no DECREF needed
if (!obj) {
return NULL;
@ -275,12 +269,7 @@ PyObject* PyTexture::composite(PyObject* cls, PyObject* args, PyObject* kwds)
std::vector<sf::Image> images;
unsigned int tex_w = 0, tex_h = 0;
// Use PyTypeCache for reliable, leak-free isinstance check
PyTypeObject* texture_type = PyTypeCache::Texture();
if (!texture_type) {
PyErr_SetString(PyExc_RuntimeError, "Failed to get Texture type from cache");
return NULL;
}
PyTypeObject* texture_type = &mcrfpydef::PyTextureType;
for (Py_ssize_t i = 0; i < count; i++) {
PyObject* item = PyList_GetItem(layers_list, i);
@ -311,7 +300,6 @@ PyObject* PyTexture::composite(PyObject* cls, PyObject* args, PyObject* kwds)
}
images.push_back(std::move(img));
}
// PyTypeCache returns borrowed reference — no DECREF needed
// Alpha-composite all layers bottom-to-top
sf::Image result;

View file

@ -60,7 +60,7 @@ public:
};
namespace mcrfpydef {
static PyTypeObject PyTextureType = {
inline PyTypeObject PyTextureType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Texture",
.tp_basicsize = sizeof(PyTextureObject),

View file

@ -48,7 +48,7 @@ public:
};
namespace mcrfpydef {
static PyTypeObject PyTimerType = {
inline PyTypeObject PyTimerType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Timer",
.tp_basicsize = sizeof(PyTimerObject),

View file

@ -1,135 +0,0 @@
// PyTypeCache.cpp - Thread-safe Python type caching implementation
#include "PyTypeCache.h"
// Static member definitions
std::atomic<PyTypeObject*> PyTypeCache::entity_type{nullptr};
std::atomic<PyTypeObject*> PyTypeCache::grid_type{nullptr};
std::atomic<PyTypeObject*> PyTypeCache::frame_type{nullptr};
std::atomic<PyTypeObject*> PyTypeCache::caption_type{nullptr};
std::atomic<PyTypeObject*> PyTypeCache::sprite_type{nullptr};
std::atomic<PyTypeObject*> PyTypeCache::texture_type{nullptr};
std::atomic<PyTypeObject*> PyTypeCache::color_type{nullptr};
std::atomic<PyTypeObject*> PyTypeCache::vector_type{nullptr};
std::atomic<PyTypeObject*> PyTypeCache::font_type{nullptr};
std::atomic<bool> PyTypeCache::initialized{false};
std::mutex PyTypeCache::init_mutex;
PyTypeObject* PyTypeCache::cacheType(PyObject* module, const char* name,
std::atomic<PyTypeObject*>& cache) {
PyObject* type_obj = PyObject_GetAttrString(module, name);
if (!type_obj) {
PyErr_Format(PyExc_RuntimeError,
"PyTypeCache: Failed to get type '%s' from module", name);
return nullptr;
}
if (!PyType_Check(type_obj)) {
Py_DECREF(type_obj);
PyErr_Format(PyExc_TypeError,
"PyTypeCache: '%s' is not a type object", name);
return nullptr;
}
// Store in cache - we keep the reference permanently
// Using memory_order_release ensures the pointer is visible to other threads
// after they see initialized=true
cache.store((PyTypeObject*)type_obj, std::memory_order_release);
return (PyTypeObject*)type_obj;
}
bool PyTypeCache::initialize(PyObject* module) {
std::lock_guard<std::mutex> lock(init_mutex);
// Double-check pattern - might have been initialized while waiting for lock
if (initialized.load(std::memory_order_acquire)) {
return true;
}
// Cache all types
if (!cacheType(module, "Entity", entity_type)) return false;
if (!cacheType(module, "Grid", grid_type)) return false;
if (!cacheType(module, "Frame", frame_type)) return false;
if (!cacheType(module, "Caption", caption_type)) return false;
if (!cacheType(module, "Sprite", sprite_type)) return false;
if (!cacheType(module, "Texture", texture_type)) return false;
if (!cacheType(module, "Color", color_type)) return false;
if (!cacheType(module, "Vector", vector_type)) return false;
if (!cacheType(module, "Font", font_type)) return false;
// Mark as initialized - release ensures all stores above are visible
initialized.store(true, std::memory_order_release);
return true;
}
void PyTypeCache::finalize() {
std::lock_guard<std::mutex> lock(init_mutex);
if (!initialized.load(std::memory_order_acquire)) {
return;
}
// Release all cached references
auto release = [](std::atomic<PyTypeObject*>& cache) {
PyTypeObject* type = cache.exchange(nullptr, std::memory_order_acq_rel);
if (type) {
Py_DECREF(type);
}
};
release(entity_type);
release(grid_type);
release(frame_type);
release(caption_type);
release(sprite_type);
release(texture_type);
release(color_type);
release(vector_type);
release(font_type);
initialized.store(false, std::memory_order_release);
}
bool PyTypeCache::isInitialized() {
return initialized.load(std::memory_order_acquire);
}
// Type accessors - lock-free reads after initialization
// Using memory_order_acquire ensures we see the pointer stored during init
PyTypeObject* PyTypeCache::Entity() {
return entity_type.load(std::memory_order_acquire);
}
PyTypeObject* PyTypeCache::Grid() {
return grid_type.load(std::memory_order_acquire);
}
PyTypeObject* PyTypeCache::Frame() {
return frame_type.load(std::memory_order_acquire);
}
PyTypeObject* PyTypeCache::Caption() {
return caption_type.load(std::memory_order_acquire);
}
PyTypeObject* PyTypeCache::Sprite() {
return sprite_type.load(std::memory_order_acquire);
}
PyTypeObject* PyTypeCache::Texture() {
return texture_type.load(std::memory_order_acquire);
}
PyTypeObject* PyTypeCache::Color() {
return color_type.load(std::memory_order_acquire);
}
PyTypeObject* PyTypeCache::Vector() {
return vector_type.load(std::memory_order_acquire);
}
PyTypeObject* PyTypeCache::Font() {
return font_type.load(std::memory_order_acquire);
}

View file

@ -1,64 +0,0 @@
#pragma once
// PyTypeCache.h - Thread-safe caching of Python type objects
//
// This module provides a centralized, thread-safe way to cache Python type
// references. It eliminates the refcount leaks from repeated
// PyObject_GetAttrString calls and is compatible with free-threading (PEP 703).
//
// Usage:
// PyTypeObject* entity_type = PyTypeCache::Entity();
// if (!entity_type) return NULL; // Error already set
//
// The cache is populated during module initialization and the types are
// held for the lifetime of the interpreter.
#include "Python.h"
#include <mutex>
#include <atomic>
class PyTypeCache {
public:
// Initialize the cache - call once during module init after types are ready
// Returns false and sets Python error on failure
static bool initialize(PyObject* module);
// Finalize - release references (call during module cleanup if needed)
static void finalize();
// Type accessors - return borrowed references (no DECREF needed)
// These are thread-safe and lock-free after initialization
static PyTypeObject* Entity();
static PyTypeObject* Grid();
static PyTypeObject* Frame();
static PyTypeObject* Caption();
static PyTypeObject* Sprite();
static PyTypeObject* Texture();
static PyTypeObject* Color();
static PyTypeObject* Vector();
static PyTypeObject* Font();
// Check if initialized
static bool isInitialized();
private:
// Cached type pointers - atomic for thread-safe reads
static std::atomic<PyTypeObject*> entity_type;
static std::atomic<PyTypeObject*> grid_type;
static std::atomic<PyTypeObject*> frame_type;
static std::atomic<PyTypeObject*> caption_type;
static std::atomic<PyTypeObject*> sprite_type;
static std::atomic<PyTypeObject*> texture_type;
static std::atomic<PyTypeObject*> color_type;
static std::atomic<PyTypeObject*> vector_type;
static std::atomic<PyTypeObject*> font_type;
// Initialization flag
static std::atomic<bool> initialized;
// Mutex for initialization (only used during init, not for reads)
static std::mutex init_mutex;
// Helper to fetch and cache a type
static PyTypeObject* cacheType(PyObject* module, const char* name,
std::atomic<PyTypeObject*>& cache);
};

View file

@ -1,7 +1,5 @@
#include "PyVector.h"
#include "PyObjectUtils.h"
#include "McRFPy_Doc.h"
#include "PyRAII.h"
#include <cmath>
PyGetSetDef PyVector::getsetters[] = {
@ -262,20 +260,16 @@ int PyVector::set_member(PyObject* obj, PyObject* value, void* closure)
PyVectorObject* PyVector::from_arg(PyObject* args)
{
// Use RAII for type reference management
PyRAII::PyTypeRef type("Vector", McRFPy_API::mcrf_module);
if (!type) {
return NULL;
}
PyTypeObject* type = &mcrfpydef::PyVectorType;
// Check if args is already a Vector instance
if (PyObject_IsInstance(args, (PyObject*)type.get())) {
if (PyObject_IsInstance(args, (PyObject*)type)) {
Py_INCREF(args); // Return new reference so caller can safely DECREF
return (PyVectorObject*)args;
}
// Create new Vector object using RAII
PyRAII::PyObjectRef obj(type->tp_alloc(type.get(), 0), true);
// Create new Vector object
PyObject* obj = type->tp_alloc(type, 0);
if (!obj) {
return NULL;
}
@ -283,25 +277,27 @@ PyVectorObject* PyVector::from_arg(PyObject* args)
// Handle different input types
if (PyTuple_Check(args)) {
// It's already a tuple, pass it directly to init
int err = init((PyVectorObject*)obj.get(), args, NULL);
int err = init((PyVectorObject*)obj, args, NULL);
if (err) {
// obj will be automatically cleaned up when it goes out of scope
Py_DECREF(obj);
return NULL;
}
} else {
// Wrap single argument in a tuple for init
PyRAII::PyObjectRef tuple(PyTuple_Pack(1, args), true);
PyObject* tuple = PyTuple_Pack(1, args);
if (!tuple) {
Py_DECREF(obj);
return NULL;
}
int err = init((PyVectorObject*)obj.get(), tuple.get(), NULL);
int err = init((PyVectorObject*)obj, tuple, NULL);
Py_DECREF(tuple);
if (err) {
Py_DECREF(obj);
return NULL;
}
}
// Release ownership and return
return (PyVectorObject*)obj.release();
return (PyVectorObject*)obj;
}
// Arithmetic operations

View file

@ -63,7 +63,7 @@ namespace mcrfpydef {
extern PyNumberMethods PyVector_as_number;
extern PySequenceMethods PyVector_as_sequence;
static PyTypeObject PyVectorType = {
inline PyTypeObject PyVectorType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Vector",
.tp_basicsize = sizeof(PyVectorObject),

View file

@ -47,7 +47,7 @@ public:
};
namespace mcrfpydef {
static PyTypeObject PyWindowType = {
inline PyTypeObject PyWindowType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Window",
.tp_basicsize = sizeof(PyWindowObject),

View file

@ -111,7 +111,7 @@ public:
extern PyMethodDef UIArc_methods[];
namespace mcrfpydef {
static PyTypeObject PyUIArcType = {
inline PyTypeObject PyUIArcType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Arc",
.tp_basicsize = sizeof(PyUIArcObject),

View file

@ -50,7 +50,7 @@ public:
extern PyMethodDef UICaption_methods[];
namespace mcrfpydef {
static PyTypeObject PyUICaptionType = {
inline PyTypeObject PyUICaptionType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Caption",
.tp_basicsize = sizeof(PyUICaptionObject),

View file

@ -100,7 +100,7 @@ public:
extern PyMethodDef UICircle_methods[];
namespace mcrfpydef {
static PyTypeObject PyUICircleType = {
inline PyTypeObject PyUICircleType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Circle",
.tp_basicsize = sizeof(PyUICircleObject),

View file

@ -9,7 +9,6 @@
#include "UIArc.h"
#include "3d/Viewport3D.h"
#include "McRFPy_API.h"
#include "PyObjectUtils.h"
#include "PythonObjectCache.h"
#include <climits>
#include <algorithm>

View file

@ -339,9 +339,6 @@ typedef struct {
} PyUICollectionIterObject;
namespace mcrfpydef {
// DEPRECATED: RET_PY_INSTANCE macro has been replaced with template functions in PyObjectUtils.h
// The macro was difficult to debug and used static type references that could cause initialization order issues.
// Use PyObjectUtils::convertDrawableToPython() or PyObjectUtils::createPyObject<T>() instead.
//TODO: add this method to class scope; move implementation to .cpp file
/*

View file

@ -4,7 +4,6 @@
#include <algorithm>
#include <cstring>
#include <libtcod.h>
#include "PyObjectUtils.h"
#include "PyVector.h"
#include "PythonObjectCache.h"
#include "PyFOV.h"

View file

@ -124,7 +124,7 @@ public:
extern PyMethodDef UIEntity_all_methods[];
namespace mcrfpydef {
static PyTypeObject PyUIEntityType = {
inline PyTypeObject PyUIEntityType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Entity",
.tp_basicsize = sizeof(PyUIEntityObject),

View file

@ -7,7 +7,6 @@
#include "UIEntity.h"
#include "UIGrid.h"
#include "McRFPy_API.h"
#include "PyTypeCache.h"
#include "PythonObjectCache.h"
#include <sstream>
#include <algorithm>
@ -49,11 +48,7 @@ PyObject* UIEntityCollectionIter::next(PyUIEntityCollectionIterObject* self)
}
// Otherwise create and return a new Python Entity object
PyTypeObject* entity_type = PyTypeCache::Entity();
if (!entity_type) {
PyErr_SetString(PyExc_RuntimeError, "Entity type not initialized in cache");
return NULL;
}
PyTypeObject* entity_type = &mcrfpydef::PyUIEntityType;
auto o = (PyUIEntityObject*)entity_type->tp_alloc(entity_type, 0);
if (!o) return NULL;
@ -119,11 +114,7 @@ PyObject* UIEntityCollection::getitem(PyUIEntityCollectionObject* self, Py_ssize
}
// Otherwise, create a new base Entity object
PyTypeObject* entity_type = PyTypeCache::Entity();
if (!entity_type) {
PyErr_SetString(PyExc_RuntimeError, "Entity type not initialized in cache");
return NULL;
}
PyTypeObject* entity_type = &mcrfpydef::PyUIEntityType;
auto o = (PyUIEntityObject*)entity_type->tp_alloc(entity_type, 0);
if (!o) return NULL;
@ -174,12 +165,8 @@ int UIEntityCollection::setitem(PyUIEntityCollectionObject* self, Py_ssize_t ind
return 0;
}
// Type checking using cached type
PyTypeObject* entity_type = PyTypeCache::Entity();
if (!entity_type) {
PyErr_SetString(PyExc_RuntimeError, "Entity type not initialized in cache");
return -1;
}
// Type checking
PyTypeObject* entity_type = &mcrfpydef::PyUIEntityType;
if (!PyObject_IsInstance(value, (PyObject*)entity_type)) {
PyErr_SetString(PyExc_TypeError, "EntityCollection can only contain Entity objects");
@ -219,9 +206,9 @@ int UIEntityCollection::contains(PyUIEntityCollectionObject* self, PyObject* val
return -1;
}
// Type checking using cached type
PyTypeObject* entity_type = PyTypeCache::Entity();
if (!entity_type || !PyObject_IsInstance(value, (PyObject*)entity_type)) {
// Type checking
PyTypeObject* entity_type = &mcrfpydef::PyUIEntityType;
if (!PyObject_IsInstance(value, (PyObject*)entity_type)) {
return 0; // Not an Entity, can't be in collection
}
@ -257,12 +244,7 @@ PyObject* UIEntityCollection::concat(PyUIEntityCollectionObject* self, PyObject*
return NULL;
}
PyTypeObject* entity_type = PyTypeCache::Entity();
if (!entity_type) {
Py_DECREF(result_list);
PyErr_SetString(PyExc_RuntimeError, "Entity type not initialized in cache");
return NULL;
}
PyTypeObject* entity_type = &mcrfpydef::PyUIEntityType;
// Add all elements from self
Py_ssize_t idx = 0;
@ -296,11 +278,7 @@ PyObject* UIEntityCollection::inplace_concat(PyUIEntityCollectionObject* self, P
return NULL;
}
PyTypeObject* entity_type = PyTypeCache::Entity();
if (!entity_type) {
PyErr_SetString(PyExc_RuntimeError, "Entity type not initialized in cache");
return NULL;
}
PyTypeObject* entity_type = &mcrfpydef::PyUIEntityType;
// First, validate ALL items before modifying anything
Py_ssize_t other_len = PySequence_Length(other);
@ -380,12 +358,7 @@ PyObject* UIEntityCollection::subscript(PyUIEntityCollectionObject* self, PyObje
return NULL;
}
PyTypeObject* entity_type = PyTypeCache::Entity();
if (!entity_type) {
Py_DECREF(result_list);
PyErr_SetString(PyExc_RuntimeError, "Entity type not initialized in cache");
return NULL;
}
PyTypeObject* entity_type = &mcrfpydef::PyUIEntityType;
auto it = self->data->begin();
for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) {
@ -474,11 +447,7 @@ int UIEntityCollection::ass_subscript(PyUIEntityCollectionObject* self, PyObject
return -1;
}
PyTypeObject* entity_type = PyTypeCache::Entity();
if (!entity_type) {
PyErr_SetString(PyExc_RuntimeError, "Entity type not initialized in cache");
return -1;
}
PyTypeObject* entity_type = &mcrfpydef::PyUIEntityType;
// Validate all items first
std::vector<std::shared_ptr<UIEntity>> new_items;
@ -577,11 +546,7 @@ PyMappingMethods UIEntityCollection::mpmethods = {
PyObject* UIEntityCollection::append(PyUIEntityCollectionObject* self, PyObject* o)
{
PyTypeObject* entity_type = PyTypeCache::Entity();
if (!entity_type) {
PyErr_SetString(PyExc_RuntimeError, "Entity type not initialized in cache");
return NULL;
}
PyTypeObject* entity_type = &mcrfpydef::PyUIEntityType;
if (!PyObject_IsInstance(o, (PyObject*)entity_type)) {
PyErr_SetString(PyExc_TypeError, "Only Entity objects can be added to EntityCollection");
@ -627,11 +592,7 @@ PyObject* UIEntityCollection::append(PyUIEntityCollectionObject* self, PyObject*
PyObject* UIEntityCollection::remove(PyUIEntityCollectionObject* self, PyObject* o)
{
PyTypeObject* entity_type = PyTypeCache::Entity();
if (!entity_type) {
PyErr_SetString(PyExc_RuntimeError, "Entity type not initialized in cache");
return NULL;
}
PyTypeObject* entity_type = &mcrfpydef::PyUIEntityType;
if (!PyObject_IsInstance(o, (PyObject*)entity_type)) {
PyErr_SetString(PyExc_TypeError, "EntityCollection.remove requires an Entity object");
@ -675,12 +636,7 @@ PyObject* UIEntityCollection::extend(PyUIEntityCollectionObject* self, PyObject*
return NULL;
}
PyTypeObject* entity_type = PyTypeCache::Entity();
if (!entity_type) {
Py_DECREF(iterator);
PyErr_SetString(PyExc_RuntimeError, "Entity type not initialized in cache");
return NULL;
}
PyTypeObject* entity_type = &mcrfpydef::PyUIEntityType;
// FIXED: Validate ALL items first before modifying anything
// (Following the pattern from inplace_concat)
@ -785,11 +741,7 @@ PyObject* UIEntityCollection::pop(PyUIEntityCollectionObject* self, PyObject* ar
list->erase(it);
// Create Python object for the entity
PyTypeObject* entity_type = PyTypeCache::Entity();
if (!entity_type) {
PyErr_SetString(PyExc_RuntimeError, "Entity type not initialized in cache");
return NULL;
}
PyTypeObject* entity_type = &mcrfpydef::PyUIEntityType;
PyUIEntityObject* py_entity = (PyUIEntityObject*)entity_type->tp_alloc(entity_type, 0);
if (!py_entity) {
@ -817,11 +769,7 @@ PyObject* UIEntityCollection::insert(PyUIEntityCollectionObject* self, PyObject*
return NULL;
}
PyTypeObject* entity_type = PyTypeCache::Entity();
if (!entity_type) {
PyErr_SetString(PyExc_RuntimeError, "Entity type not initialized in cache");
return NULL;
}
PyTypeObject* entity_type = &mcrfpydef::PyUIEntityType;
if (!PyObject_IsInstance(o, (PyObject*)entity_type)) {
PyErr_SetString(PyExc_TypeError, "EntityCollection.insert requires an Entity object");
@ -874,11 +822,7 @@ PyObject* UIEntityCollection::index_method(PyUIEntityCollectionObject* self, PyO
return NULL;
}
PyTypeObject* entity_type = PyTypeCache::Entity();
if (!entity_type) {
PyErr_SetString(PyExc_RuntimeError, "Entity type not initialized in cache");
return NULL;
}
PyTypeObject* entity_type = &mcrfpydef::PyUIEntityType;
if (!PyObject_IsInstance(value, (PyObject*)entity_type)) {
PyErr_SetString(PyExc_TypeError, "EntityCollection.index requires an Entity object");
@ -910,8 +854,8 @@ PyObject* UIEntityCollection::count(PyUIEntityCollectionObject* self, PyObject*
return NULL;
}
PyTypeObject* entity_type = PyTypeCache::Entity();
if (!entity_type || !PyObject_IsInstance(value, (PyObject*)entity_type)) {
PyTypeObject* entity_type = &mcrfpydef::PyUIEntityType;
if (!PyObject_IsInstance(value, (PyObject*)entity_type)) {
return PyLong_FromLong(0);
}
@ -969,11 +913,7 @@ PyObject* UIEntityCollection::find(PyUIEntityCollectionObject* self, PyObject* a
std::string pattern(name);
bool has_wildcard = (pattern.find('*') != std::string::npos);
PyTypeObject* entity_type = PyTypeCache::Entity();
if (!entity_type) {
PyErr_SetString(PyExc_RuntimeError, "Entity type not initialized in cache");
return NULL;
}
PyTypeObject* entity_type = &mcrfpydef::PyUIEntityType;
if (has_wildcard) {
PyObject* results = PyList_New(0);

View file

@ -76,7 +76,7 @@ public:
extern PyMethodDef UIFrame_methods[];
namespace mcrfpydef {
static PyTypeObject PyUIFrameType = {
inline PyTypeObject PyUIFrameType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Frame",
.tp_basicsize = sizeof(PyUIFrameObject),

View file

@ -4,7 +4,6 @@
#include "McRFPy_API.h"
#include "PythonObjectCache.h"
#include "PyAlignment.h"
#include "PyTypeCache.h" // Thread-safe cached Python types
#include "UIEntity.h"
#include "Profiler.h"
#include "PyFOV.h"
@ -1928,16 +1927,7 @@ PyObject* UIGrid::py_entities_in_radius(PyUIGridObject* self, PyObject* args, Py
PyObject* result = PyList_New(entities.size());
if (!result) return PyErr_NoMemory();
// Cache Entity type for efficiency
static PyTypeObject* cached_entity_type = nullptr;
if (!cached_entity_type) {
cached_entity_type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity");
if (!cached_entity_type) {
Py_DECREF(result);
return NULL;
}
Py_INCREF(cached_entity_type);
}
PyTypeObject* entity_type = &mcrfpydef::PyUIEntityType;
for (size_t i = 0; i < entities.size(); i++) {
auto& entity = entities[i];
@ -1948,7 +1938,7 @@ PyObject* UIGrid::py_entities_in_radius(PyUIGridObject* self, PyObject* args, Py
PyList_SET_ITEM(result, i, entity->self);
} else {
// Create new Python Entity wrapper
auto pyEntity = (PyUIEntityObject*)cached_entity_type->tp_alloc(cached_entity_type, 0);
auto pyEntity = (PyUIEntityObject*)entity_type->tp_alloc(entity_type, 0);
if (!pyEntity) {
Py_DECREF(result);
return PyErr_NoMemory();

View file

@ -255,7 +255,7 @@ public:
extern PyMethodDef UIGrid_all_methods[];
namespace mcrfpydef {
static PyTypeObject PyUIGridType = {
inline PyTypeObject PyUIGridType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Grid",
.tp_basicsize = sizeof(PyUIGridObject),

View file

@ -97,7 +97,7 @@ public:
extern PyMethodDef UILine_methods[];
namespace mcrfpydef {
static PyTypeObject PyUILineType = {
inline PyTypeObject PyUILineType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Line",
.tp_basicsize = sizeof(PyUILineObject),

View file

@ -83,7 +83,7 @@ public:
extern PyMethodDef UISprite_methods[];
namespace mcrfpydef {
static PyTypeObject PyUISpriteType = {
inline PyTypeObject PyUISpriteType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Sprite",
.tp_basicsize = sizeof(PyUISpriteObject),