Add "Python-Binding-Layer"
parent
1db761b051
commit
f60d04f762
|
|
@ -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
|
||||||
Loading…
Reference in New Issue