feat: Exit on first Python callback exception (closes #133)

By default, McRogueFace now exits with code 1 on the first unhandled
exception in timer, click, key, or animation callbacks. This prevents
repeated exception output that wastes resources in AI-driven development.

Changes:
- Add exit_on_exception config flag (default: true)
- Add --continue-after-exceptions CLI flag to preserve old behavior
- Update exception handlers in Timer, PyCallable, and Animation
- Signal game loop via McRFPy_API atomic flags
- Return proper exit code from main()

Before: Timer exceptions repeated 1000+ times until timeout
After: Single traceback, clean exit with code 1

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
John McCardle 2025-11-26 10:26:30 -05:00
commit 19ded088b0
12 changed files with 160 additions and 8 deletions

View file

@ -1,4 +1,6 @@
#include "PyCallable.h"
#include "McRFPy_API.h"
#include "GameEngine.h"
PyCallable::PyCallable(PyObject* _target)
{
@ -51,9 +53,14 @@ void PyClickCallable::call(sf::Vector2f mousepos, std::string button, std::strin
PyObject* retval = PyCallable::call(args, NULL);
if (!retval)
{
std::cout << "ClickCallable has raised an exception. It's going to STDERR and being dropped:" << std::endl;
std::cerr << "Click callback raised an exception:" << std::endl;
PyErr_Print();
PyErr_Clear();
// Check if we should exit on exception
if (McRFPy_API::game && McRFPy_API::game->getConfig().exit_on_exception) {
McRFPy_API::signalPythonException();
}
} else if (retval != Py_None)
{
std::cout << "ClickCallable returned a non-None value. It's not an error, it's just not being saved or used." << std::endl;
@ -81,9 +88,14 @@ void PyKeyCallable::call(std::string key, std::string action)
PyObject* retval = PyCallable::call(args, NULL);
if (!retval)
{
std::cout << "KeyCallable has raised an exception. It's going to STDERR and being dropped:" << std::endl;
std::cerr << "Key callback raised an exception:" << std::endl;
PyErr_Print();
PyErr_Clear();
// Check if we should exit on exception
if (McRFPy_API::game && McRFPy_API::game->getConfig().exit_on_exception) {
McRFPy_API::signalPythonException();
}
} else if (retval != Py_None)
{
std::cout << "KeyCallable returned a non-None value. It's not an error, it's just not being saved or used." << std::endl;