mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-08-23 23:16:31 +00:00
Add a static context for connecting Qt signals for python extensions
This commit is contained in:
@@ -129,6 +129,7 @@ struct OutputRedirector
|
||||
uint64_t dummy;
|
||||
PythonContext *context;
|
||||
};
|
||||
bool selfDeleting;
|
||||
int isStdError;
|
||||
bool block;
|
||||
rdcstr extension;
|
||||
@@ -147,6 +148,7 @@ PyObject *PythonContext::main_dict = NULL;
|
||||
PyObject *PythonContext::m_DebugPy = NULL;
|
||||
PyObject *PythonContext::m_CallWrapper = NULL;
|
||||
PyObject *PythonContext::m_CallWrapperGlobals = NULL;
|
||||
PythonContext *PythonContext::m_ExtensionContext = NULL;
|
||||
QMap<rdcstr, PyObject *> PythonContext::extensions;
|
||||
|
||||
static PyObject *current_global_handle = NULL;
|
||||
@@ -550,6 +552,7 @@ void PythonContext::GlobalInit(PersistantConfig &config)
|
||||
|
||||
OutputRedirector *output = (OutputRedirector *)redirector;
|
||||
output->isStdError = 0;
|
||||
output->selfDeleting = false;
|
||||
output->context = NULL;
|
||||
output->block = false;
|
||||
|
||||
@@ -558,6 +561,7 @@ void PythonContext::GlobalInit(PersistantConfig &config)
|
||||
|
||||
output = (OutputRedirector *)redirector;
|
||||
output->isStdError = 1;
|
||||
output->selfDeleting = false;
|
||||
output->context = NULL;
|
||||
output->block = false;
|
||||
}
|
||||
@@ -897,6 +901,9 @@ except:
|
||||
|
||||
// release GIL so that python work can now happen on any thread
|
||||
PyEval_SaveThread();
|
||||
|
||||
// this will leak effectively
|
||||
m_ExtensionContext = new PythonContext(true, NULL);
|
||||
}
|
||||
|
||||
bool PythonContext::initialised()
|
||||
@@ -904,7 +911,7 @@ bool PythonContext::initialised()
|
||||
return main_dict != NULL;
|
||||
}
|
||||
|
||||
PythonContext::PythonContext(QObject *parent) : QObject(parent)
|
||||
PythonContext::PythonContext(bool extensionContext, QObject *parent) : QObject(parent)
|
||||
{
|
||||
if(!initialised())
|
||||
return;
|
||||
@@ -915,8 +922,6 @@ PythonContext::PythonContext(QObject *parent) : QObject(parent)
|
||||
// clone our own local context
|
||||
context_namespace = PyDict_Copy(main_dict);
|
||||
|
||||
PyObject *rlcompleter = PyImport_ImportModule("rlcompleter");
|
||||
|
||||
// for compatibility with earlier versions of python that took a char * instead of const char *
|
||||
char noparams[1] = "";
|
||||
|
||||
@@ -929,10 +934,13 @@ PythonContext::PythonContext(QObject *parent) : QObject(parent)
|
||||
|
||||
OutputRedirector *output = (OutputRedirector *)redirector;
|
||||
output->context = this;
|
||||
output->selfDeleting = !extensionContext;
|
||||
output->block = false;
|
||||
Py_DECREF(redirector);
|
||||
}
|
||||
|
||||
PyObject *rlcompleter = PyImport_ImportModule("rlcompleter");
|
||||
|
||||
if(rlcompleter)
|
||||
{
|
||||
PyObject *Completer = PyObject_SafeGetAttrString(rlcompleter, "Completer");
|
||||
@@ -1237,6 +1245,7 @@ QString PythonContext::LoadExtension(ICaptureContext &ctx, const rdcstr &extensi
|
||||
|
||||
OutputRedirector *redir = (OutputRedirector *)ext_context;
|
||||
redir->isStdError = 0;
|
||||
redir->selfDeleting = false;
|
||||
redir->context = NULL;
|
||||
redir->block = false;
|
||||
redir->extension = extension;
|
||||
@@ -1415,7 +1424,8 @@ void PythonContext::executeString(const QString &filename, const QString &source
|
||||
if(!initialised())
|
||||
{
|
||||
FlushOutput();
|
||||
emit exception(lit("SystemError"), tr("Python integration failed to initialise."), -1, {});
|
||||
emit exception(QString(), lit("SystemError"), tr("Python integration failed to initialise."),
|
||||
-1, {});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1534,7 +1544,7 @@ void PythonContext::executeString(const QString &filename, const QString &source
|
||||
if(caughtException)
|
||||
{
|
||||
FlushOutput();
|
||||
emit exception(typeStr, valueStr, finalLine, frames);
|
||||
emit exception(QString(), typeStr, valueStr, finalLine, frames);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1550,8 +1560,8 @@ void PythonContext::executeFile(const QString &filename)
|
||||
if(!f.exists())
|
||||
{
|
||||
FlushOutput();
|
||||
emit exception(lit("FileNotFoundError"), tr("No such file or directory: %1").arg(filename), -1,
|
||||
{});
|
||||
emit exception(QString(), lit("FileNotFoundError"),
|
||||
tr("No such file or directory: %1").arg(filename), -1, {});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1564,7 +1574,8 @@ void PythonContext::executeFile(const QString &filename)
|
||||
else
|
||||
{
|
||||
FlushOutput();
|
||||
emit exception(lit("IOError"), QFormatStr("%1: %2").arg(f.errorString()).arg(filename), -1, {});
|
||||
emit exception(QString(), lit("IOError"),
|
||||
QFormatStr("%1: %2").arg(f.errorString()).arg(filename), -1, {});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1573,7 +1584,8 @@ void PythonContext::setGlobal(const char *varName, const char *typeName, void *o
|
||||
if(!initialised())
|
||||
{
|
||||
FlushOutput();
|
||||
emit exception(lit("SystemError"), tr("Python integration failed to initialise."), -1, {});
|
||||
emit exception(QString(), lit("SystemError"), tr("Python integration failed to initialise."),
|
||||
-1, {});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1592,7 +1604,7 @@ void PythonContext::setGlobal(const char *varName, const char *typeName, void *o
|
||||
if(ret != 0)
|
||||
{
|
||||
FlushOutput();
|
||||
emit exception(lit("RuntimeError"),
|
||||
emit exception(QString(), lit("RuntimeError"),
|
||||
tr("Failed to set variable '%1' of type '%2'")
|
||||
.arg(QString::fromUtf8(varName))
|
||||
.arg(QString::fromUtf8(typeName)),
|
||||
@@ -1854,28 +1866,33 @@ void PythonContext::outputTick()
|
||||
{
|
||||
QMutexLocker lock(&outputMutex);
|
||||
|
||||
if(!outstr.isEmpty())
|
||||
for(QString &extension : outputCaches.keys())
|
||||
{
|
||||
emit textOutput(false, outstr);
|
||||
}
|
||||
OutputPair &o = outputCaches[extension];
|
||||
|
||||
if(!errstr.isEmpty())
|
||||
{
|
||||
emit textOutput(true, errstr);
|
||||
}
|
||||
if(!o.outstr.isEmpty())
|
||||
{
|
||||
emit textOutput(extension, false, o.outstr);
|
||||
}
|
||||
|
||||
outstr.clear();
|
||||
errstr.clear();
|
||||
if(!o.errstr.isEmpty())
|
||||
{
|
||||
emit textOutput(extension, true, o.errstr);
|
||||
}
|
||||
|
||||
o.outstr.clear();
|
||||
o.errstr.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void PythonContext::addText(bool isStdError, const QString &output)
|
||||
void PythonContext::addText(QString context, bool isStdError, const QString &output)
|
||||
{
|
||||
QMutexLocker lock(&outputMutex);
|
||||
|
||||
if(isStdError)
|
||||
errstr += output;
|
||||
outputCaches[context].errstr += output;
|
||||
else
|
||||
outstr += output;
|
||||
outputCaches[context].outstr += output;
|
||||
}
|
||||
|
||||
void PythonContext::setPyGlobal(const char *varName, PyObject *obj)
|
||||
@@ -1883,7 +1900,8 @@ void PythonContext::setPyGlobal(const char *varName, PyObject *obj)
|
||||
if(!initialised())
|
||||
{
|
||||
FlushOutput();
|
||||
emit exception(lit("SystemError"), tr("Python integration failed to initialise."), -1, {});
|
||||
emit exception(QString(), lit("SystemError"), tr("Python integration failed to initialise."),
|
||||
-1, {});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1900,7 +1918,7 @@ void PythonContext::setPyGlobal(const char *varName, PyObject *obj)
|
||||
return;
|
||||
|
||||
FlushOutput();
|
||||
emit exception(lit("RuntimeError"),
|
||||
emit exception(QString(), lit("RuntimeError"),
|
||||
tr("Failed to set variable '%1'").arg(QString::fromUtf8(varName)), -1, {});
|
||||
}
|
||||
|
||||
@@ -1908,7 +1926,7 @@ void PythonContext::outstream_del(PyObject *self)
|
||||
{
|
||||
OutputRedirector *redirector = (OutputRedirector *)self;
|
||||
|
||||
if(redirector)
|
||||
if(redirector && redirector->selfDeleting)
|
||||
{
|
||||
PythonContext *context = redirector->context;
|
||||
|
||||
@@ -1933,6 +1951,7 @@ PyObject *PythonContext::outstream_write(PyObject *self, PyObject *args)
|
||||
if(redirector)
|
||||
{
|
||||
PythonContext *context = redirector->context;
|
||||
QString extension = redirector->extension;
|
||||
// most likely this is NULL because the sys.stdout override is static and shared amongst
|
||||
// contexts. So look up the global variable that stores the context
|
||||
if(context == NULL)
|
||||
@@ -1950,7 +1969,10 @@ PyObject *PythonContext::outstream_write(PyObject *self, PyObject *args)
|
||||
OutputRedirector *global =
|
||||
(OutputRedirector *)PyDict_GetItemString(globals, "_renderdoc_internal");
|
||||
if(global)
|
||||
{
|
||||
context = global->context;
|
||||
extension = global->extension;
|
||||
}
|
||||
}
|
||||
|
||||
Py_XDECREF(globals);
|
||||
@@ -1974,10 +1996,16 @@ PyObject *PythonContext::outstream_write(PyObject *self, PyObject *args)
|
||||
|
||||
if(context)
|
||||
{
|
||||
context->addText(redirector->isStdError ? true : false, QString::fromUtf8(text));
|
||||
context->addText(extension, redirector->isStdError ? true : false, QString::fromUtf8(text));
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!extension.isEmpty())
|
||||
{
|
||||
m_ExtensionContext->addText(extension, redirector->isStdError ? true : false,
|
||||
QString::fromUtf8(text));
|
||||
}
|
||||
|
||||
// if context is still NULL we're running in the extension context
|
||||
rdcstr message = text;
|
||||
|
||||
@@ -2303,10 +2331,17 @@ extern "C" void HandleException(PyObject *global_handle)
|
||||
if(redirector && redirector->context)
|
||||
{
|
||||
redirector->context->FlushOutput();
|
||||
emit redirector->context->exception(typeStr, valueStr, finalLine, frames);
|
||||
emit redirector->context->exception(redirector->extension, typeStr, valueStr, finalLine, frames);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(redirector && !redirector->extension.empty())
|
||||
{
|
||||
PythonContext::GetExtensionContext()->FlushOutput();
|
||||
emit PythonContext::GetExtensionContext()->exception(redirector->extension, typeStr, valueStr,
|
||||
finalLine, frames);
|
||||
}
|
||||
|
||||
// if still NULL we're running in the extension context
|
||||
rdcstr exString;
|
||||
|
||||
|
||||
@@ -47,8 +47,9 @@ private:
|
||||
// on and needs to finish executing after the external code is done with the context
|
||||
~PythonContext();
|
||||
|
||||
explicit PythonContext(bool extensionContext, QObject *parent);
|
||||
public:
|
||||
explicit PythonContext(QObject *parent = NULL);
|
||||
explicit PythonContext(QObject *parent = NULL) : PythonContext(false, parent) {}
|
||||
void Finish();
|
||||
|
||||
PyThreadState *GetExecutingThreadState() { return m_State; }
|
||||
@@ -94,7 +95,7 @@ public:
|
||||
if(obj)
|
||||
setPyGlobal(varName, obj);
|
||||
else
|
||||
emit exception(lit("RuntimeError"),
|
||||
emit exception(QString(), lit("RuntimeError"),
|
||||
tr("Failed to set variable '%1' of type '%2'")
|
||||
.arg(QString::fromUtf8(varName))
|
||||
.arg(QString::fromUtf8(typeName)),
|
||||
@@ -114,10 +115,14 @@ public:
|
||||
int currentLine() { return location.line; }
|
||||
static void AddDebuggableThread();
|
||||
static void RemoveDebuggableThread();
|
||||
|
||||
static PythonContext *GetExtensionContext() { return m_ExtensionContext; }
|
||||
|
||||
signals:
|
||||
void traceLine(const QString &file, int line);
|
||||
void exception(const QString &type, const QString &value, int finalLine, QList<QString> frames);
|
||||
void textOutput(bool isStdError, const QString &output);
|
||||
void exception(const QString &extension, const QString &type, const QString &value, int finalLine,
|
||||
QList<QString> frames);
|
||||
void textOutput(const QString &extension, bool isStdError, const QString &output);
|
||||
|
||||
public slots:
|
||||
void executeString(const QString &source);
|
||||
@@ -139,6 +144,10 @@ private:
|
||||
static PyObject *m_CallWrapper;
|
||||
static PyObject *m_CallWrapperGlobals;
|
||||
|
||||
// a statically created PythonContext for extension events/output.
|
||||
// each extension has its own dictionary but this is used so that users can connect to it and receieve events
|
||||
static PythonContext *m_ExtensionContext;
|
||||
|
||||
// the list of extension objects, to be able to reload them
|
||||
static QMap<rdcstr, PyObject *> extensions;
|
||||
|
||||
@@ -170,10 +179,15 @@ private:
|
||||
|
||||
QTimer *outputTicker = NULL;
|
||||
QMutex outputMutex;
|
||||
QString outstr, errstr;
|
||||
|
||||
struct OutputPair
|
||||
{
|
||||
QString outstr, errstr;
|
||||
};
|
||||
QMap<QString, OutputPair> outputCaches;
|
||||
|
||||
void outputTick();
|
||||
void addText(bool isStdError, const QString &output);
|
||||
void addText(QString extension, bool isStdError, const QString &output);
|
||||
|
||||
// Python callbacks
|
||||
static void outstream_del(PyObject *self);
|
||||
|
||||
@@ -673,31 +673,31 @@ int main(int argc, char *argv[])
|
||||
|
||||
py.ctx().setGlobal("pyrenderdoc", (ICaptureContext *)&ctx);
|
||||
|
||||
QObject::connect(
|
||||
&py.ctx(), &PythonContext::exception,
|
||||
[&pythonExited](const QString &type, const QString &value, int, QList<QString> frames) {
|
||||
if(type == lit("SystemExit"))
|
||||
{
|
||||
pythonExited = true;
|
||||
return;
|
||||
}
|
||||
QObject::connect(&py.ctx(), &PythonContext::exception,
|
||||
[&pythonExited](const QString &, const QString &type, const QString &value,
|
||||
int, QList<QString> frames) {
|
||||
if(type == lit("SystemExit"))
|
||||
{
|
||||
pythonExited = true;
|
||||
return;
|
||||
}
|
||||
|
||||
QString exString;
|
||||
QString exString;
|
||||
|
||||
if(!frames.isEmpty())
|
||||
{
|
||||
exString += tr("Traceback (most recent call last):\n");
|
||||
for(const QString &f : frames)
|
||||
exString += QFormatStr(" %1\n").arg(f);
|
||||
}
|
||||
if(!frames.isEmpty())
|
||||
{
|
||||
exString += tr("Traceback (most recent call last):\n");
|
||||
for(const QString &f : frames)
|
||||
exString += QFormatStr(" %1\n").arg(f);
|
||||
}
|
||||
|
||||
exString += QFormatStr("%1: %2\n").arg(type).arg(value);
|
||||
exString += QFormatStr("%1: %2\n").arg(type).arg(value);
|
||||
|
||||
qCritical("%s", exString.toUtf8().data());
|
||||
});
|
||||
qCritical("%s", exString.toUtf8().data());
|
||||
});
|
||||
|
||||
QObject::connect(&py.ctx(), &PythonContext::textOutput,
|
||||
[](bool isStdError, const QString &output) {
|
||||
[](const QString &, bool isStdError, const QString &output) {
|
||||
if(isStdError)
|
||||
qCritical("%s", output.toUtf8().data());
|
||||
else
|
||||
|
||||
@@ -1052,6 +1052,11 @@ PythonShell::PythonShell(ICaptureContext &ctx, QWidget *parent)
|
||||
|
||||
enableButtons(true);
|
||||
|
||||
QObject::connect(PythonContext::GetExtensionContext(), &PythonContext::textOutput, this,
|
||||
&PythonShell::textOutput);
|
||||
QObject::connect(PythonContext::GetExtensionContext(), &PythonContext::exception, this,
|
||||
&PythonShell::exception);
|
||||
|
||||
// reset output to default
|
||||
on_clear_clicked();
|
||||
on_newScript_clicked();
|
||||
@@ -1282,8 +1287,8 @@ void PythonShell::traceLine(const QString &file, int line)
|
||||
scriptEditor->markerAdd(line > 0 ? line - 1 : 0, CURRENT_MARKER + 1);
|
||||
}
|
||||
|
||||
void PythonShell::exception(const QString &type, const QString &value, int finalLine,
|
||||
QList<QString> frames)
|
||||
void PythonShell::exception(const QString &extension, const QString &type, const QString &value,
|
||||
int finalLine, QList<QString> frames)
|
||||
{
|
||||
QTextEdit *out = ui->scriptOutput;
|
||||
if(QObject::sender() == (QObject *)interactiveContext)
|
||||
@@ -1307,7 +1312,7 @@ void PythonShell::exception(const QString &type, const QString &value, int final
|
||||
appendText(out, exString);
|
||||
}
|
||||
|
||||
void PythonShell::textOutput(bool isStdError, const QString &output)
|
||||
void PythonShell::textOutput(const QString &extension, bool isStdError, const QString &output)
|
||||
{
|
||||
QTextEdit *out = ui->scriptOutput;
|
||||
if(QObject::sender() == (QObject *)interactiveContext)
|
||||
@@ -1431,9 +1436,10 @@ void PythonShell::refreshCurrentHelp()
|
||||
|
||||
ui->helpText->clear();
|
||||
|
||||
QObject::connect(
|
||||
context, &PythonContext::textOutput,
|
||||
[this](bool isStdError, const QString &output) { appendText(ui->helpText, output); });
|
||||
QObject::connect(context, &PythonContext::textOutput,
|
||||
[this](const QString &, bool isStdError, const QString &output) {
|
||||
appendText(ui->helpText, output);
|
||||
});
|
||||
|
||||
context->executeString(lit(R"(
|
||||
try:
|
||||
|
||||
@@ -78,8 +78,9 @@ private slots:
|
||||
void interactive_keypress(QKeyEvent *e);
|
||||
void helpSearch_keypress(QKeyEvent *e);
|
||||
void traceLine(const QString &file, int line);
|
||||
void exception(const QString &type, const QString &value, int finalLine, QList<QString> frames);
|
||||
void textOutput(bool isStdError, const QString &output);
|
||||
void exception(const QString &extension, const QString &type, const QString &value, int finalLine,
|
||||
QList<QString> frames);
|
||||
void textOutput(const QString &extension, bool isStdError, const QString &output);
|
||||
void editor_contextMenu(const QPoint &pos);
|
||||
|
||||
private:
|
||||
|
||||
Reference in New Issue
Block a user