Add "Python-Binding-Layer"

John McCardle 2025-10-25 20:58:22 +00:00
parent 1db761b051
commit f60d04f762
1 changed files with 187 additions and 0 deletions

187
Python-Binding-Layer.-.md Normal file

@ -0,0 +1,187 @@
# 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](../../issues/126) - Generate Perfectly Consistent Python Interface (Tier 1)
- [#109](../../issues/109) - Vector Convenience Methods
- [#101](../../issues/101) - Standardize Constructor Arguments
- [#92](../../issues/92) - Inline C++ Documentation System
- [#91](../../issues/91) - Generate Python Type Stub Files (.pyi)
**Key Files:**
- `src/McRFPy_API.h` / `src/McRFPy_API.cpp` - Main Python module definition
- `src/PyObjectUtils.h` - Utility functions for Python/C++ conversion
- `src/UIDrawable.h` - `RET_PY_INSTANCE` macro 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:
```cpp
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:
```cpp
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:
```cpp
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:**
1. Add to PyGetSetDef array
2. Implement getter/setter functions
3. Encode closure parameter
4. Add inline documentation
5. Test with Python
### Type Preservation in Collections
**Challenge:** Shared pointers can lose Python type information
**Solution:**
- Use `RET_PY_INSTANCE` when returning from collections
- Maintain Python object references when needed
- See [#112](../../issues/112) for object splitting bug details
### Constructor Standardization
**Current state:** Inconsistent constructor patterns across types
**Planned:** [#101](../../issues/101) - Standardize all constructors to accept:
- Position as `(x, y)` tuple or separate `x, y` args
- Size as `(w, h)` tuple or separate `w, h` args
- Consistent default values (usually `(0, 0)`)
## Key Subsystems
### PyArgHelpers
Standardized argument parsing for tuples vs separate args:
```cpp
// 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*.cpp` for constructor consistency
### Documentation Extraction
**Pipeline:**
1. C++ docstrings in PyMethodDef/PyGetSetDef arrays
2. Compilation embeds docstrings in module
3. `tools/generate_dynamic_docs.py` extracts via introspection
4. Generates `docs/api_reference_dynamic.html`
**Format:** See CLAUDE.md "Inline C++ Documentation Format" section
## Current Issues & Limitations
**Consistency Issues:**
- [#126](../../issues/126): Need automated generation for perfect consistency
- [#101](../../issues/101): Constructor arguments vary by type
- [#109](../../issues/109): Vector lacks `[0]`, `[1]` indexing
**Type Preservation:**
- Collections can lose Python derived types
- Workaround: `RET_PY_INSTANCE` macro
- 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.md` for complete patterns
- See [#126](../../issues/126) for automated generation progress