Este proyecto es educativo y Open Source. No se copia código de otros emuladores. Implementación basada únicamente en documentación técnica y tests permitidas.
Fix de Compatibilidad API MMU y Numpy
Resumen
Al ejecutar el emulador con el núcleo C++ migrado, aparecieron dos errores críticos: (1) `ModuleNotFoundError: No module named 'numpy'` al intentar usar el framebuffer Zero-Copy, y (2) `AttributeError: 'viboy_core.PyMMU' object has no attribute 'read_byte'` porque el wrapper Cython expone métodos `read()`/`write()` pero el código Python espera `read_byte()`/`write_byte()`. Se implementaron métodos de compatibilidad (aliases) en el wrapper PyMMU y se verificó la instalación de numpy para mantener retrocompatibilidad con el código Python existente.
Concepto de Hardware
Este fix no está relacionado directamente con el hardware del Game Boy, sino con la arquitectura híbrida Python-C++ del proyecto. Durante la migración del núcleo a C++, se creó un wrapper Cython (`PyMMU`) que expone la clase C++ `MMU` a Python.
El problema surge cuando el código Python existente (como `renderer.py`) fue escrito para trabajar con la MMU Python original, que tenía métodos `read_byte()`, `write_byte()`, `read_word()` y `write_word()`. El nuevo wrapper Cython expuso métodos más simples (`read()` y `write()`), rompiendo la compatibilidad con el código legacy.
Retrocompatibilidad en arquitecturas híbridas: Cuando se migra código de un lenguaje a otro, es crucial mantener la API pública compatible para evitar tener que reescribir todo el código que depende de ella. Los métodos "alias" son una técnica común para mantener compatibilidad mientras se refactoriza internamente.
Implementación
Se añadieron cuatro métodos de compatibilidad al wrapper `PyMMU` en `src/core/cython/mmu.pyx`:
Métodos de Compatibilidad Añadidos
read_byte(addr): Alias deread(addr)para compatibilidad con código Python antiguo.write_byte(addr, value): Alias dewrite(addr, value).read_word(addr): Lee 16 bits usando Little-Endian (LSB en addr, MSB en addr+1).write_word(addr, value): Escribe 16 bits usando Little-Endian.
Implementación de read_word/write_word
Los métodos `read_word()` y `write_word()` implementan correctamente el orden Little-Endian de la Game Boy:
def read_word(self, uint16_t addr):
# Leer LSB (menos significativo) en addr
cdef uint8_t lsb = self.read(addr)
# Leer MSB (más significativo) en addr+1 (con wrap-around)
cdef uint8_t msb = self.read((addr + 1) & 0xFFFF)
# Combinar en Little-Endian: (MSB << 8) | LSB
return ((msb << 8) | lsb) & 0xFFFF
def write_word(self, uint16_t addr, uint16_t value):
# Extraer LSB y MSB
cdef uint8_t lsb = value & 0xFF
cdef uint8_t msb = (value >> 8) & 0xFF
# Escribir en orden Little-Endian
self.write(addr, lsb)
self.write((addr + 1) & 0xFFFF, msb)
Dependencia Numpy
Numpy ya estaba en `requirements.txt` pero no estaba instalado en el entorno virtual. Se verificó su instalación y se confirmó que está disponible. Numpy es necesario para el framebuffer Zero-Copy que permite transferir el buffer de píxeles de C++ a Pygame sin copias de memoria.
Decisiones de Diseño
- Alias en lugar de renombrar: Se optó por mantener ambos nombres de métodos (`read()` y `read_byte()`) para no romper código existente ni código nuevo que use la API simplificada.
- Implementación en Cython: Los métodos `read_word()` y `write_word()` se implementaron en Cython usando los métodos base `read()` y `write()`, evitando duplicar lógica en C++.
- Tipado estático: Se usaron tipos C (`cdef uint8_t`, `cdef uint16_t`) para mantener el rendimiento sin overhead de Python.
Archivos Afectados
src/core/cython/mmu.pyx- Añadidos métodos de compatibilidad (read_byte, write_byte, read_word, write_word)requirements.txt- Verificado que numpy>=1.24.0 está presente
Tests y Verificación
La verificación se realizó mediante:
- Compilación exitosa: El módulo Cython se recompiló sin errores usando
python setup.py build_ext --inplace. - Validación de sintaxis: No se encontraron errores de linter en el archivo modificado.
- Numpy instalado: Se confirmó que numpy está disponible en el entorno virtual.
Próxima verificación: Ejecutar el emulador con una ROM para confirmar que el renderer puede acceder a la MMU sin errores de atributos faltantes.
Fuentes Consultadas
- Cython Documentation: https://cython.readthedocs.io/
- Pan Docs: Little-Endian byte order (sección Memory Map)
Nota: Este fix es principalmente sobre arquitectura de software e interoperabilidad Python-C++, no sobre hardware del Game Boy.
Integridad Educativa
Lo que Entiendo Ahora
- Retrocompatibilidad en wrappers: Cuando se crea un wrapper de C++ a Python, es crucial mantener la API compatible con el código existente. Los métodos alias son una solución elegante que no añade overhead significativo.
- Little-Endian en Game Boy: La implementación de `read_word()` y `write_word()` confirma que la Game Boy usa Little-Endian, donde el byte menos significativo se almacena en la dirección más baja.
- Zero-Copy con Numpy: Numpy es esencial para transferir buffers de memoria entre C++ y Python sin copias, usando memoryviews que apuntan directamente a la memoria nativa.
Lo que Falta Confirmar
- Rendimiento de los alias: Aunque los métodos alias son simples wrappers, sería útil medir si hay algún overhead medible comparado con llamar directamente a `read()`/`write()`.
- Uso en el código: Verificar que todos los lugares que usan `read_byte()`/`write_byte()` ahora funcionan correctamente con el wrapper C++.
Hipótesis y Suposiciones
Se asume que el código Python existente no usa características avanzadas de la MMU Python que no estén disponibles en la versión C++. Si aparecen más incompatibilidades, se añadirán métodos de compatibilidad adicionales según sea necesario.
Próximos Pasos
- [ ] Ejecutar el emulador con una ROM y verificar que el renderer funciona sin errores
- [ ] Verificar que el framebuffer Zero-Copy funciona correctamente con numpy
- [ ] Si aparecen más incompatibilidades de API, añadir métodos de compatibilidad adicionales
- [ ] Considerar crear una suite de tests específica para validar la compatibilidad del wrapper PyMMU