Python Binding Layer
The Python Binding Layer exposes C++ engine functionality to Python using Python's C API. This system allows game logic to be written in Python while maintaining C++ rendering performance.
Quick Reference
Related Issues:
- #126 - Generate Perfectly Consistent Python Interface (Tier 1)
- #109 - Vector Convenience Methods
- #101 - Standardize Constructor Arguments
- #92 - Inline C++ Documentation System
- #91 - Generate Python Type Stub Files (.pyi)
Key Files:
src/McRFPy_API.h/src/McRFPy_API.cpp- Main Python module definitionsrc/PyObjectUtils.h- Utility functions for Python/C++ conversionsrc/UIDrawable.h-RET_PY_INSTANCEmacro pattern- Individual class binding files:
src/UI*.cpp(PyGetSetDef arrays)
Reference Documentation:
PYTHON_BINDING_PATTERNS.md- Comprehensive pattern reference (repo root)- Adding-Python-Bindings - Step-by-step workflow guide
Architecture Overview
Module Structure
mcrfpy (C extension module)
├── Types (Frame, Caption, Sprite, Grid, Entity, etc)
├── Functions (createScene, setScene, animate, etc)
├── Constants (SFML key codes, etc)
└── Submodules
└── libtcod (TCOD bindings)
Entry Point: src/McRFPy_API.cpp::PyInit_mcrfpy()
Binding Patterns
Pattern 1: PyGetSetDef for Properties
Properties exposed via getter/setter arrays:
PyGetSetDef PyUISprite::getsetters[] = {
{"x", (getter)Drawable::get_member, (setter)Drawable::set_member,
"X coordinate", (void*)SPRITE_X},
{"texture", (getter)PyUISprite::get_texture, (setter)PyUISprite::set_texture,
"Sprite texture", NULL},
{NULL} // Sentinel
};
Closure Parameter: Used to identify which property is being accessed
- Simple types: Integer values (0, 1, 2, 3)
- UIDrawable types:
(void*)((intptr_t)PyObjectsEnum::TYPE << 8 | member_index)
See: PYTHON_BINDING_PATTERNS.md for complete closure encoding reference
Pattern 2: PyMethodDef for Methods
Methods exposed via method definition arrays:
PyMethodDef PyUIGrid::methods[] = {
{"at", (PyCFunction)PyUIGrid::at, METH_VARARGS | METH_KEYWORDS,
"at(pos: tuple) -> GridPoint\n\n"
"Access grid cell at position.\n\n"
"Args:\n"
" pos: (x, y) tuple\n\n"
"Returns:\n"
" GridPoint object at that position"},
{NULL}
};
Inline Documentation: Docstrings extracted by tools/generate_dynamic_docs.py
Pattern 3: RET_PY_INSTANCE Macro
Converting C++ objects to Python requires type-aware allocation:
RET_PY_INSTANCE(target);
// Expands to switch on target->derived_type():
// - Allocates correct Python type (Frame, Caption, Sprite, Grid)
// - Assigns shared_ptr to data member
// - Returns PyObject*
File: src/UIDrawable.h::RET_PY_INSTANCE macro definition
Common Patterns
Adding a Property
See Adding-Python-Bindings for complete step-by-step workflow.
Quick reference:
- Add to PyGetSetDef array
- Implement getter/setter functions
- Encode closure parameter
- Add inline documentation
- Test with Python
Type Preservation in Collections
Challenge: Shared pointers can lose Python type information
Solution:
- Use
RET_PY_INSTANCEwhen returning from collections - Maintain Python object references when needed
- See #112 for object splitting bug details
Constructor Standardization
Current state: Inconsistent constructor patterns across types
Planned: #101 - Standardize all constructors to accept:
- Position as
(x, y)tuple or separatex, yargs - Size as
(w, h)tuple or separatew, hargs - Consistent default values (usually
(0, 0))
Key Subsystems
PyArgHelpers
Standardized argument parsing for tuples vs separate args:
// Accept both (x, y) and x, y formats
PyArgParseTuple_IntIntHelper(args, kwds, x, y, "position", "x", "y");
Files:
src/PyArgHelpers.h- Helper function definitions- Used throughout
src/UI*.cppfor constructor consistency
Documentation Extraction
Pipeline:
- C++ docstrings in PyMethodDef/PyGetSetDef arrays
- Compilation embeds docstrings in module
tools/generate_dynamic_docs.pyextracts via introspection- Generates
docs/api_reference_dynamic.html
Format: See CLAUDE.md "Inline C++ Documentation Format" section
Current Issues & Limitations
Consistency Issues:
- #126: Need automated generation for perfect consistency
- #101: Constructor arguments vary by type
- #109: Vector lacks
[0],[1]indexing
Type Preservation:
- Collections can lose Python derived types
- Workaround:
RET_PY_INSTANCEmacro - Long-term: Better type tracking in C++
Related Systems
- UI-Component-Hierarchy - Classes exposed to Python
- Grid-System - Grid/Entity Python API
- Animation-System -
animate()function binding
Design Decisions
Why Python C API vs pybind11/SWIG?
- Fine-grained control over type system
- Direct integration with CPython internals
- No third-party dependencies
- Performance: Zero-overhead abstraction
Tradeoffs:
- More verbose than pybind11
- Manual memory management required
- Type checking done manually
- But: Full control, no "magic"
Next Steps:
- Review Adding-Python-Bindings workflow
- Study
PYTHON_BINDING_PATTERNS.mdfor complete patterns - See #126 for automated generation progress