diff --git a/Python-Binding-Layer.-.md b/Python-Binding-Layer.-.md new file mode 100644 index 0000000..34bfada --- /dev/null +++ b/Python-Binding-Layer.-.md @@ -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 \ No newline at end of file diff --git a/Python-Binding-Layer.md b/Python-Binding-Layer.md deleted file mode 100644 index a419850..0000000 --- a/Python-Binding-Layer.md +++ /dev/null @@ -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 \ No newline at end of file