Update Python Binding Layer wiki: modernize API examples, add MCRF_* doc macros, current type list
parent
9c742d4f72
commit
421f74639a
2 changed files with 238 additions and 187 deletions
238
Python-Binding-Layer.-.md
Normal file
238
Python-Binding-Layer.-.md
Normal file
|
|
@ -0,0 +1,238 @@
|
||||||
|
# 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
|
||||||
|
- [#109](../issues/109) - Vector Convenience Methods
|
||||||
|
- [#92](../issues/92) - Inline C++ Documentation System (Closed - Implemented)
|
||||||
|
|
||||||
|
**Key Files:**
|
||||||
|
- `src/McRFPy_API.h` / `src/McRFPy_API.cpp` - Main Python module definition
|
||||||
|
- `src/McRFPy_Doc.h` - Documentation macro system (MCRF_METHOD, MCRF_PROPERTY, etc.)
|
||||||
|
- `src/PyObjectUtils.h` - Utility functions for Python/C++ conversion
|
||||||
|
- `src/UIDrawable.h` - `RET_PY_INSTANCE` macro pattern
|
||||||
|
- Individual class binding files: `src/Py*.cpp`
|
||||||
|
|
||||||
|
**Reference Documentation:**
|
||||||
|
- [[Adding-Python-Bindings]] - Step-by-step workflow guide
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
### Module Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
mcrfpy (C extension module)
|
||||||
|
|-- Types
|
||||||
|
| |-- UI: Frame, Caption, Sprite, Grid, Entity
|
||||||
|
| |-- Geometry: Arc, Circle, Line
|
||||||
|
| |-- Grid Layers: TileLayer, ColorLayer
|
||||||
|
| |-- Data: Color, Vector, Texture, Font
|
||||||
|
| |-- Scene: Scene (with children, on_key)
|
||||||
|
| |-- Timer: Timer (with stop, pause, resume)
|
||||||
|
| |-- Pathfinding: AStarPath, DijkstraMap
|
||||||
|
| |-- Enums: Key, MouseButton, InputState, Easing
|
||||||
|
| +-- Tiled: TileSetFile, WangSet, LdtkProject, AutoRuleSet
|
||||||
|
|
|
||||||
|
|-- Module Functions
|
||||||
|
| |-- current_scene (property)
|
||||||
|
| |-- step(dt)
|
||||||
|
| |-- start_benchmark(), end_benchmark(), log_benchmark()
|
||||||
|
| +-- find() (scene lookup)
|
||||||
|
|
|
||||||
|
+-- Submodules
|
||||||
|
+-- automation (screenshots, mouse, keyboard)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Entry Point:** `src/McRFPy_API.cpp::PyInit_mcrfpy()`
|
||||||
|
|
||||||
|
### Binding Patterns
|
||||||
|
|
||||||
|
#### Pattern 1: PyGetSetDef for Properties
|
||||||
|
|
||||||
|
Properties exposed via getter/setter arrays:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
PyGetSetDef UISprite::getsetters[] = {
|
||||||
|
{"x", (getter)Drawable::get_member, (setter)Drawable::set_member,
|
||||||
|
MCRF_PROPERTY(x, "X coordinate of the sprite."),
|
||||||
|
(void*)SPRITE_X},
|
||||||
|
{"texture", (getter)UISprite::get_texture, (setter)UISprite::set_texture,
|
||||||
|
MCRF_PROPERTY(texture, "Sprite texture reference."),
|
||||||
|
NULL},
|
||||||
|
{NULL} // Sentinel
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Pattern 2: PyMethodDef for Methods
|
||||||
|
|
||||||
|
Methods exposed via method definition arrays:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
PyMethodDef UIGrid::methods[] = {
|
||||||
|
{"at", (PyCFunction)UIGrid::at, METH_VARARGS | METH_KEYWORDS,
|
||||||
|
MCRF_METHOD(Grid, at,
|
||||||
|
MCRF_SIG("(x: int, y: int)", "GridPoint"),
|
||||||
|
MCRF_DESC("Access grid cell at position."),
|
||||||
|
MCRF_ARGS_START
|
||||||
|
MCRF_ARG("x", "X coordinate")
|
||||||
|
MCRF_ARG("y", "Y coordinate")
|
||||||
|
MCRF_RETURNS("GridPoint object at that position")
|
||||||
|
)},
|
||||||
|
{NULL}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 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`
|
||||||
|
|
||||||
|
## Documentation Macro System
|
||||||
|
|
||||||
|
Since October 2025, all Python-facing documentation uses macros from `src/McRFPy_Doc.h`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "McRFPy_Doc.h"
|
||||||
|
|
||||||
|
// Method documentation
|
||||||
|
MCRF_METHOD(ClassName, method_name,
|
||||||
|
MCRF_SIG("(arg: type)", "return_type"),
|
||||||
|
MCRF_DESC("What the method does."),
|
||||||
|
MCRF_ARGS_START
|
||||||
|
MCRF_ARG("arg", "Argument description")
|
||||||
|
MCRF_RETURNS("Return value description")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Property documentation
|
||||||
|
MCRF_PROPERTY(property_name, "Description of the property.")
|
||||||
|
```
|
||||||
|
|
||||||
|
This ensures documentation stays in sync with code. See `tools/generate_dynamic_docs.py` for the extraction pipeline.
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Type Preservation in Collections
|
||||||
|
|
||||||
|
**Challenge:** Shared pointers can lose Python type information when retrieved from collections.
|
||||||
|
|
||||||
|
**Solution:** Use `RET_PY_INSTANCE` when returning from collections, which checks `derived_type()` to allocate the correct Python wrapper.
|
||||||
|
|
||||||
|
### Constructor Keywords
|
||||||
|
|
||||||
|
All public types use keyword arguments:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# UI types
|
||||||
|
frame = mcrfpy.Frame(pos=(100, 200), size=(300, 150))
|
||||||
|
caption = mcrfpy.Caption(text="Hello", pos=(10, 10))
|
||||||
|
sprite = mcrfpy.Sprite(x=50, y=50, sprite_index=0)
|
||||||
|
|
||||||
|
# Grid types
|
||||||
|
grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600))
|
||||||
|
entity = mcrfpy.Entity(grid_pos=(10, 10), sprite_index=42)
|
||||||
|
|
||||||
|
# Data types
|
||||||
|
color = mcrfpy.Color(255, 128, 0, 200)
|
||||||
|
texture = mcrfpy.Texture("assets/sprites/tileset.png", 16, 16)
|
||||||
|
|
||||||
|
# Scene and Timer
|
||||||
|
scene = mcrfpy.Scene("my_scene")
|
||||||
|
timer = mcrfpy.Timer("my_timer", callback, 500) # callback(timer, runtime)
|
||||||
|
```
|
||||||
|
|
||||||
|
### PyArgHelpers
|
||||||
|
|
||||||
|
Standardized argument parsing for tuples vs separate args:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "PyArgHelpers.h"
|
||||||
|
|
||||||
|
// Accept both (x, y) and x, y formats
|
||||||
|
PyArgParseTuple_IntIntHelper(args, kwds, x, y, "position", "x", "y");
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Subsystems
|
||||||
|
|
||||||
|
### Scene System
|
||||||
|
|
||||||
|
Scenes are first-class Python objects:
|
||||||
|
|
||||||
|
```python
|
||||||
|
scene = mcrfpy.Scene("game")
|
||||||
|
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(100, 100)))
|
||||||
|
scene.on_key = lambda key, action: None # Key enum, InputState enum
|
||||||
|
mcrfpy.current_scene = scene
|
||||||
|
```
|
||||||
|
|
||||||
|
### Animation System
|
||||||
|
|
||||||
|
Animation is a method on UIDrawable objects:
|
||||||
|
|
||||||
|
```python
|
||||||
|
frame.animate("x", 500.0, 2.0, mcrfpy.Easing.EASE_IN_OUT)
|
||||||
|
frame.animate("opacity", 0.0, 1.0, mcrfpy.Easing.LINEAR, callback=on_done)
|
||||||
|
# callback receives (target, property_name, final_value)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Timer System
|
||||||
|
|
||||||
|
Timers are objects with control methods:
|
||||||
|
|
||||||
|
```python
|
||||||
|
t = mcrfpy.Timer("update", callback, 100) # callback(timer, runtime_ms)
|
||||||
|
t.pause()
|
||||||
|
t.resume()
|
||||||
|
t.stop()
|
||||||
|
t.restart()
|
||||||
|
# Properties: name, interval, callback, active, paused, stopped, remaining, once
|
||||||
|
```
|
||||||
|
|
||||||
|
### Input Enums
|
||||||
|
|
||||||
|
```python
|
||||||
|
mcrfpy.Key.W # Keyboard keys
|
||||||
|
mcrfpy.MouseButton.LEFT # Mouse buttons
|
||||||
|
mcrfpy.InputState.PRESSED # Input states (PRESSED, RELEASED, HOLD)
|
||||||
|
mcrfpy.Easing.EASE_IN_OUT # Animation easing functions
|
||||||
|
```
|
||||||
|
|
||||||
|
## Current Issues & Limitations
|
||||||
|
|
||||||
|
**Consistency:**
|
||||||
|
- [#126](../issues/126): Automated generation for perfect consistency
|
||||||
|
- [#109](../issues/109): Vector lacks `[0]`, `[1]` indexing
|
||||||
|
|
||||||
|
**Type Preservation:**
|
||||||
|
- Collections can lose Python derived types
|
||||||
|
- Workaround: `RET_PY_INSTANCE` macro
|
||||||
|
|
||||||
|
## Design Decisions
|
||||||
|
|
||||||
|
**Why Python C API vs pybind11/SWIG?**
|
||||||
|
- Fine-grained control over type system
|
||||||
|
- Direct integration with CPython internals
|
||||||
|
- No third-party dependencies
|
||||||
|
- Zero-overhead abstraction
|
||||||
|
|
||||||
|
**Tradeoffs:**
|
||||||
|
- More verbose than pybind11
|
||||||
|
- Manual memory management required
|
||||||
|
- But: Full control, no "magic"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Next Steps:**
|
||||||
|
- Review [[Adding-Python-Bindings]] for the step-by-step workflow
|
||||||
|
- See `docs/api_reference_dynamic.html` for the generated API reference
|
||||||
|
|
@ -1,187 +0,0 @@
|
||||||
# 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…
Add table
Add a link
Reference in a new issue