1 Python-Binding-Layer
John McCardle edited this page 2025-10-25 20:58:22 +00:00

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 - Generate Perfectly Consistent Python Interface (Tier 1)
  • #109 - Vector Convenience Methods
  • #101 - Standardize Constructor Arguments
  • #92 - Inline C++ Documentation System
  • #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:

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:

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:

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 for object splitting bug details

Constructor Standardization

Current state: Inconsistent constructor patterns across types

Planned: #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:

// 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: Need automated generation for perfect consistency
  • #101: Constructor arguments vary by type
  • #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++

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 for automated generation progress