From 6beb3ee39fd5f60feb2e7bbfd1f0e6fef8f11475 Mon Sep 17 00:00:00 2001 From: John McCardle Date: Sat, 7 Feb 2026 23:49:47 +0000 Subject: [PATCH] Update Python Binding Layer --- ...ding-Layer.-.md => Python-Binding-Layer.md | 474 +++++++++--------- 1 file changed, 237 insertions(+), 237 deletions(-) rename Python-Binding-Layer.-.md => Python-Binding-Layer.md (96%) diff --git a/Python-Binding-Layer.-.md b/Python-Binding-Layer.md similarity index 96% rename from Python-Binding-Layer.-.md rename to Python-Binding-Layer.md index 34bfada..183aa43 100644 --- a/Python-Binding-Layer.-.md +++ b/Python-Binding-Layer.md @@ -1,238 +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 +# 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 \ No newline at end of file