Fix #219: Add threading support with mcrfpy.lock() context manager

Enables background Python threads to safely modify UI objects by
synchronizing with the render loop at frame boundaries.

Implementation:
- FrameLock class provides mutex/condvar synchronization
- GIL released during window.display() allowing background threads to run
- Safe window opens between frames for synchronized UI updates
- mcrfpy.lock() context manager blocks until safe window, then executes
- Main thread detection: lock() is a no-op when called from callbacks
  or script initialization (already synchronized)

Usage:
    import threading
    import mcrfpy

    def background_worker():
        with mcrfpy.lock():  # Blocks until safe
            player.x = new_x  # Safe to modify UI

    threading.Thread(target=background_worker).start()

The lock works transparently from any context - background threads get
actual synchronization, main thread calls (callbacks, init) get no-op.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
John McCardle 2026-01-19 23:37:49 -05:00
commit 257e52327b
5 changed files with 269 additions and 1 deletions

25
src/PyLock.h Normal file
View file

@ -0,0 +1,25 @@
#pragma once
// #219 - Thread synchronization context manager for mcrfpy.lock()
#include <Python.h>
// Forward declaration
class GameEngine;
// PyLockContext - the context manager object returned by mcrfpy.lock()
typedef struct {
PyObject_HEAD
bool acquired; // Track if we've acquired the lock
} PyLockContextObject;
// The type object for the context manager
extern PyTypeObject PyLockContextType;
// Module initialization function - adds lock() function to module
namespace PyLock {
// Create the lock() function that returns a context manager
PyObject* lock(PyObject* self, PyObject* args);
// Initialize the type (call PyType_Ready)
int init();
}