TypeError: TaskManager.keyboardInterruptHandler()

I’m trying to write a compact ImGui server interface, so I’m reinventing my own bike on the C++ side (but what else…). However, I came across an error that I partially understand, but do not understand how to resolve it, since my idea partially works. I use a python function callback in C++, the code works great if the function has a function type, if it has a method type, that is, it is a member of a class. I get these kinds of spam messages.

TypeError: TaskManager.keyboardInterruptHandler() missing 1 required positional argument: 'stackFrame'

My base class code python, where there are two functions, one global (works), the other is a class method (does not work).

from direct.showbase.ShowBase import ShowBase
from panda3d.imgui import PImGui

# Work
def instrution(index):
    print(index)

class DemoImGui(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        base.exitFunc = self.exitfunc

        # Create context ImGui
        display_region = base.cam2d.node().get_display_region(0)
        PImGui.init(display_region, self.instrution);

    # Not work
    def instrution(self, index):
        print(index)

    def exitfunc(self):
        PImGui.destroy()

app = DemoImGui()
app.run()

The actual call to these functions is on the C++ side.

void PImGui::do_callback(CallbackData* cbdata){
    
    PyGILState_STATE gstate;
    gstate = PyGILState_Ensure();

    PyObject* arglist = Py_BuildValue("(i)", index);
    PyObject* result = PyObject_CallObject(instrution, arglist);
    
    Py_DECREF(arglist);
    
    if (result == NULL) { PyErr_Print(); } 
    else { Py_DECREF(result); }

    PyGILState_Release(gstate);
    
    cbdata->upcall();
}

This looks like a problem with threads, since disabling signal.SIG_IGN processing fixes the situation, but the function is not called in the derived thread.

I would guess you have forgotten to use Py_INCREF on the instrution object passed in from Python before storing it in C++.

Otherwise I need to see more code. Is this in a custom CallbackObject?

I have a test call that works if it was made from the interpreter, the problem starts if I use a callback on the C++ side.

I’ve cleaned up my code:

p3imgui.h

#ifndef P3IMGUI_H
#define P3IMGUI_H

#include "dtoolbase.h"
#include "displayRegion.h"
#include "callbackObject.h"
#include "callbackData.h"

#include <Python.h>
#include <stdexcept>
#include <cstdio>

static PT(DisplayRegion) display_region;
static PyObject* instrution = nullptr;
static bool is_context = false;
static int index = 0;

BEGIN_PUBLISH

class PImGui : public CallbackObject{
    PUBLISHED:
    public:
        ALLOC_DELETED_CHAIN(PImGui);
        PImGui(const PImGui&) = delete;
        PImGui& operator=(const PImGui&) = delete;
        static void init(PT(DisplayRegion) display_region, PyObject* function);
        static void call_test();
        void do_callback(CallbackData* cbdata);
    private:
        PImGui();
        ~PImGui();
};

END_PUBLISH

#endif

p3imgui.cpp

#include "p3imgui.h"

PImGui::PImGui(){
    is_context = true;
}

PImGui::~PImGui(){
}

void PImGui::init(PT(DisplayRegion) dr, PyObject *fun){
    if (is_context != true) {
        if (Py_TYPE(fun) == &PyFunction_Type || Py_TYPE(fun) == &PyMethod_Type) {
            instrution = fun;
            display_region = dr;
            
            PImGui* callback = new PImGui();
            display_region->set_draw_callback(callback);
        } else {
            try { throw std::runtime_error ("PImGui: error, the second argument is not a function or method"); } 
            catch (const std::exception& error) { puts(error.what()); std::abort(); }
        }
    } else {
        std::cout << "PImGui: warning, the context has already been created" << std::endl;
    }
}

void PImGui::do_callback(CallbackData* cbdata){
    
    PyGILState_STATE gstate = PyGILState_Ensure();
    
    PyObject* arglist = Py_BuildValue("(i)", index);
    PyObject* result = PyObject_CallObject(instrution, arglist);
    
    Py_DECREF(arglist);

    if (result == NULL) { PyErr_Print(); } 
    else { Py_DECREF(result); }

    PyGILState_Release(gstate);
    
    cbdata->upcall();
}

void PImGui::call_test(){
    
    PyGILState_STATE gstate = PyGILState_Ensure();

    PyObject* arglist = Py_BuildValue("(i)", index);
    PyObject* result = PyObject_CallObject(instrution, arglist);
    
    Py_DECREF(arglist);

    if (result == NULL) { PyErr_Print(); } 
    else { Py_DECREF(result); }

    PyGILState_Release(gstate);
}

main.py

from direct.showbase.ShowBase import ShowBase
from panda3d.imgui import PImGui

# Work
def instrution(index):
    print("Call function: ", index)

class DemoImGui(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        display_region = base.cam2d.node().get_display_region(0)

        PImGui.init(display_region, self.instrution);
        PImGui.call_test()

    # Not work do_callback(C++) / Work use call_test()
    def instrution(self, index):
        print("Call method: ", index)

app = DemoImGui()
app.run()

I’m not doing the check you specified, because in the init function, I’m checking the type.

I’ve narrowed down the range of problems, it seems to be a ShowBase problem after all and somehow related to the system’s signal processing.

This works great in an improvised alternative ShowBase class.

from panda3d.imgui import PImGui
from panda3d.core import *

# Work
def instrution(index):
    print("Call function: ", index)

class MyShowBase():
    def __init__(self):

        pipe = GraphicsPipeSelection.get_global_ptr().make_default_pipe()
        self.engine = GraphicsEngine.get_global_ptr()

        fb_prop = FrameBufferProperties()
        fb_prop.rgb_color = 1
        fb_prop.color_bits = 24
        fb_prop.depth_bits = 24
        fb_prop.back_buffers = 1

        self.win = self.engine.make_output(pipe, name="window", sort=0, fb_prop=fb_prop,
                                           win_prop=WindowProperties(size=(800, 600)),
                                           flags=GraphicsPipe.BF_require_window)

        self.win.set_clear_color_active(True)
        self.win.set_clear_color((0.5, 0.5, 0.5, 1))

        self.render = NodePath("render")
        self.cam = self.render.attach_new_node(Camera("camera"))
        self.cam.node().set_lens(PerspectiveLens())

        self.dr = self.win.make_display_region()
        PImGui.init(self.dr, self.instrution);
        self.dr.camera = self.cam

    # Work
    def instrution(self, index):
        print("Call method: ", index)

    def run(self):
        while not self.win.is_closed():
            self.engine.render_frame()

app = MyShowBase()
app.run()

The issue is exactly as I suggested in my previous post. You’re not incrementing the reference count when storing instrution on the C++ side. Try:

instrution = Py_NewRef(fun);

@rdb
Thank you very much, using the link counting system provided by the python API fixed the situation.