Added JS script monitoring thread to prevent bad scripts from hanging the gui.

This commit is contained in:
harry 2024-02-04 22:09:59 -05:00
parent 5495c7eddc
commit 1fc813803e
2 changed files with 211 additions and 48 deletions

View File

@ -131,12 +131,12 @@ bool EmuScriptObject::paused()
return FCEUI_EmulationPaused() != 0; return FCEUI_EmulationPaused() != 0;
} }
//---------------------------------------------------- //----------------------------------------------------
int EmuScriptObject::framecount() int EmuScriptObject::frameCount()
{ {
return FCEUMOV_GetFrame(); return FCEUMOV_GetFrame();
} }
//---------------------------------------------------- //----------------------------------------------------
int EmuScriptObject::lagcount() int EmuScriptObject::lagCount()
{ {
return FCEUI_GetLagCount(); return FCEUI_GetLagCount();
} }
@ -146,7 +146,7 @@ bool EmuScriptObject::lagged()
return FCEUI_GetLagged(); return FCEUI_GetLagged();
} }
//---------------------------------------------------- //----------------------------------------------------
void EmuScriptObject::setlagflag(bool flag) void EmuScriptObject::setLagFlag(bool flag)
{ {
FCEUI_SetLagFlag(flag); FCEUI_SetLagFlag(flag);
} }
@ -200,14 +200,14 @@ void EmuScriptObject::speedMode(const QString& mode)
FCEUD_SetEmulationSpeed(speed); FCEUD_SetEmulationSpeed(speed);
} }
//---------------------------------------------------- //----------------------------------------------------
void EmuScriptObject::registerBefore(const QJSValue& func) void EmuScriptObject::registerBeforeFrame(const QJSValue& func)
{ {
script->registerBefore(func); script->registerBeforeEmuFrame(func);
} }
//---------------------------------------------------- //----------------------------------------------------
void EmuScriptObject::registerAfter(const QJSValue& func) void EmuScriptObject::registerAfterFrame(const QJSValue& func)
{ {
script->registerAfter(func); script->registerAfterEmuFrame(func);
} }
//---------------------------------------------------- //----------------------------------------------------
void EmuScriptObject::registerStop(const QJSValue& func) void EmuScriptObject::registerStop(const QJSValue& func)
@ -278,7 +278,7 @@ static void addressReadCallback(unsigned int address, unsigned int value, void *
{ {
QJSValueList args = { address, value }; QJSValueList args = { address, value };
func->call(args); mem->getScript()->runFunc( *func, args);
} }
} }
} }
@ -295,7 +295,7 @@ static void addressWriteCallback(unsigned int address, unsigned int value, void
{ {
QJSValueList args = { address, value }; QJSValueList args = { address, value };
func->call(args); mem->getScript()->runFunc( *func, args);
} }
} }
} }
@ -312,7 +312,7 @@ static void addressExecCallback(unsigned int address, unsigned int value, void *
{ {
QJSValueList args = { address, value }; QJSValueList args = { address, value };
func->call(args); mem->getScript()->runFunc( *func, args);
} }
} }
} }
@ -699,6 +699,11 @@ void QtScriptInstance::shutdownEngine()
delete onScriptStopCallback; delete onScriptStopCallback;
onScriptStopCallback = nullptr; onScriptStopCallback = nullptr;
} }
if (onGuiUpdateCallback != nullptr)
{
delete onGuiUpdateCallback;
onGuiUpdateCallback = nullptr;
}
if (engine != nullptr) if (engine != nullptr)
{ {
@ -849,7 +854,7 @@ void QtScriptInstance::loadUI(const QString& uiFilePath)
#endif #endif
} }
//---------------------------------------------------- //----------------------------------------------------
void QtScriptInstance::registerBefore(const QJSValue& func) void QtScriptInstance::registerBeforeEmuFrame(const QJSValue& func)
{ {
if (onFrameBeginCallback != nullptr) if (onFrameBeginCallback != nullptr)
{ {
@ -858,7 +863,7 @@ void QtScriptInstance::registerBefore(const QJSValue& func)
onFrameBeginCallback = new QJSValue(func); onFrameBeginCallback = new QJSValue(func);
} }
//---------------------------------------------------- //----------------------------------------------------
void QtScriptInstance::registerAfter(const QJSValue& func) void QtScriptInstance::registerAfterEmuFrame(const QJSValue& func)
{ {
if (onFrameFinishCallback != nullptr) if (onFrameFinishCallback != nullptr)
{ {
@ -876,6 +881,15 @@ void QtScriptInstance::registerStop(const QJSValue& func)
onScriptStopCallback = new QJSValue(func); onScriptStopCallback = new QJSValue(func);
} }
//---------------------------------------------------- //----------------------------------------------------
void QtScriptInstance::registerGuiUpdate(const QJSValue& func)
{
if (onGuiUpdateCallback != nullptr)
{
delete onGuiUpdateCallback;
}
onGuiUpdateCallback = new QJSValue(func);
}
//----------------------------------------------------
void QtScriptInstance::print(const QString& msg) void QtScriptInstance::print(const QString& msg)
{ {
if (dialog) if (dialog)
@ -933,6 +947,26 @@ void QtScriptInstance::printSymbols(QJSValue& val, int iter)
} }
} }
//---------------------------------------------------- //----------------------------------------------------
int QtScriptInstance::runFunc(QJSValue &func, const QJSValueList& args)
{
int retval = 0;
auto state = getExecutionState();
state->start();
QJSValue callResult = func.call(args);
state->stop();
if (callResult.isError())
{
retval = -1;
running = false;
print(callResult.toString());
}
return retval;
}
//----------------------------------------------------
int QtScriptInstance::call(const QString& funcName, const QJSValueList& args) int QtScriptInstance::call(const QString& funcName, const QJSValueList& args)
{ {
if (engine == nullptr) if (engine == nullptr)
@ -947,19 +981,10 @@ int QtScriptInstance::call(const QString& funcName, const QJSValueList& args)
QJSValue func = engine->globalObject().property(funcName); QJSValue func = engine->globalObject().property(funcName);
FCEU_WRAPPER_LOCK(); FCEU_WRAPPER_LOCK();
QJSValue callResult = func.call(args); int retval = runFunc(func, args);
FCEU_WRAPPER_UNLOCK(); FCEU_WRAPPER_UNLOCK();
if (callResult.isError()) return retval;
{
print(callResult.toString());
}
else
{
//printf("Script Call Success!\n");
}
return 0;
} }
//---------------------------------------------------- //----------------------------------------------------
void QtScriptInstance::stopRunning() void QtScriptInstance::stopRunning()
@ -969,12 +994,7 @@ void QtScriptInstance::stopRunning()
{ {
if (onScriptStopCallback != nullptr && onScriptStopCallback->isCallable()) if (onScriptStopCallback != nullptr && onScriptStopCallback->isCallable())
{ {
QJSValue callResult = onScriptStopCallback->call(); runFunc( *onScriptStopCallback );
if (callResult.isError())
{
print(callResult.toString());
}
} }
running = false; running = false;
@ -987,13 +1007,7 @@ void QtScriptInstance::onFrameBegin()
{ {
if (running && onFrameBeginCallback != nullptr && onFrameBeginCallback->isCallable()) if (running && onFrameBeginCallback != nullptr && onFrameBeginCallback->isCallable())
{ {
QJSValue callResult = onFrameBeginCallback->call(); runFunc( *onFrameBeginCallback );
if (callResult.isError())
{
print(callResult.toString());
running = false;
}
} }
} }
//---------------------------------------------------- //----------------------------------------------------
@ -1001,14 +1015,59 @@ void QtScriptInstance::onFrameFinish()
{ {
if (running && onFrameFinishCallback != nullptr && onFrameFinishCallback->isCallable()) if (running && onFrameFinishCallback != nullptr && onFrameFinishCallback->isCallable())
{ {
QJSValue callResult = onFrameFinishCallback->call(); runFunc( *onFrameFinishCallback );
}
}
//----------------------------------------------------
void QtScriptInstance::onGuiUpdate()
{
if (running && onGuiUpdateCallback != nullptr && onGuiUpdateCallback->isCallable())
{
runFunc( *onGuiUpdateCallback );
}
}
//----------------------------------------------------
ScriptExecutionState* QtScriptInstance::getExecutionState()
{
ScriptExecutionState* state;
if (callResult.isError()) if (onEmulationThread())
{
state = &emuFuncState;
}
else
{
state = &guiFuncState;
}
return state;
}
//----------------------------------------------------
void QtScriptInstance::checkForHang()
{
static constexpr uint64_t funcTimeoutMs = 1000;
if ( guiFuncState.isRunning() )
{
uint64_t timeRunningMs = guiFuncState.timeRunning();
if (timeRunningMs > funcTimeoutMs)
{ {
print(callResult.toString()); printf("Interrupted GUI Thread Script Function\n");
running = false; engine->setInterrupted(true);
} }
} }
if ( emuFuncState.isRunning() )
{
uint64_t timeRunningMs = emuFuncState.timeRunning();
if (timeRunningMs > funcTimeoutMs)
{
printf("Interrupted Emulation Thread Script Function\n");
engine->setInterrupted(true);
}
}
} }
//---------------------------------------------------- //----------------------------------------------------
QString QtScriptInstance::openFileBrowser(const QString& initialPath) QString QtScriptInstance::openFileBrowser(const QString& initialPath)
@ -1069,10 +1128,19 @@ QtScriptManager::QtScriptManager(QObject* parent)
: QObject(parent) : QObject(parent)
{ {
_instance = this; _instance = this;
monitorThread = new ScriptMonitorThread_t();
monitorThread->start();
periodicUpdateTimer = new QTimer(this);
connect( periodicUpdateTimer, &QTimer::timeout, this, &QtScriptManager::guiUpdate );
periodicUpdateTimer->start(50); // ~20hz
} }
//---------------------------------------------------- //----------------------------------------------------
QtScriptManager::~QtScriptManager() QtScriptManager::~QtScriptManager()
{ {
monitorThread->requestInterruption();
monitorThread->wait();
_instance = nullptr; _instance = nullptr;
//printf("QtScriptManager destroyed\n"); //printf("QtScriptManager destroyed\n");
} }
@ -1096,11 +1164,13 @@ void QtScriptManager::destroy(void)
//---------------------------------------------------- //----------------------------------------------------
void QtScriptManager::addScriptInstance(QtScriptInstance* script) void QtScriptManager::addScriptInstance(QtScriptInstance* script)
{ {
FCEU::autoScopedLock autoLock(scriptListMutex);
scriptList.push_back(script); scriptList.push_back(script);
} }
//---------------------------------------------------- //----------------------------------------------------
void QtScriptManager::removeScriptInstance(QtScriptInstance* script) void QtScriptManager::removeScriptInstance(QtScriptInstance* script)
{ {
FCEU::autoScopedLock autoLock(scriptListMutex);
auto it = scriptList.begin(); auto it = scriptList.begin();
while (it != scriptList.end()) while (it != scriptList.end())
@ -1136,6 +1206,41 @@ void QtScriptManager::frameFinishedUpdate()
FCEU_WRAPPER_UNLOCK(); FCEU_WRAPPER_UNLOCK();
} }
//---------------------------------------------------- //----------------------------------------------------
void QtScriptManager::guiUpdate()
{
FCEU_WRAPPER_LOCK();
for (auto script : scriptList)
{
script->onGuiUpdate();
}
FCEU_WRAPPER_UNLOCK();
}
//----------------------------------------------------
//---- Qt Script Monitor Thread
//----------------------------------------------------
ScriptMonitorThread_t::ScriptMonitorThread_t(QObject *parent)
: QThread(parent)
{
}
//----------------------------------------------------
void ScriptMonitorThread_t::run()
{
//printf("Script Monitor Thread is Running...\n");
QtScriptManager* manager = QtScriptManager::getInstance();
while (!isInterruptionRequested())
{
manager->scriptListMutex.lock();
for (auto script : manager->scriptList)
{
script->checkForHang();
}
manager->scriptListMutex.unlock();
msleep(100);
}
//printf("Script Monitor Thread is Stopping...\n");
}
//----------------------------------------------------
//---- Qt Script Dialog Window //---- Qt Script Dialog Window
//---------------------------------------------------- //----------------------------------------------------
QScriptDialog_t::QScriptDialog_t(QWidget *parent) QScriptDialog_t::QScriptDialog_t(QWidget *parent)

View File

@ -26,8 +26,10 @@
#include <QTreeWidget> #include <QTreeWidget>
#include <QTreeWidgetItem> #include <QTreeWidgetItem>
#include <QJSEngine> #include <QJSEngine>
#include <QThread>
#include "Qt/main.h" #include "Qt/main.h"
#include "utils/mutex.h"
#include "utils/timeStamp.h" #include "utils/timeStamp.h"
class QScriptDialog_t; class QScriptDialog_t;
@ -85,13 +87,13 @@ public slots:
Q_INVOKABLE void pause(); Q_INVOKABLE void pause();
Q_INVOKABLE void unpause(); Q_INVOKABLE void unpause();
Q_INVOKABLE bool paused(); Q_INVOKABLE bool paused();
Q_INVOKABLE int framecount(); Q_INVOKABLE int frameCount();
Q_INVOKABLE int lagcount(); Q_INVOKABLE int lagCount();
Q_INVOKABLE bool lagged(); Q_INVOKABLE bool lagged();
Q_INVOKABLE void setlagflag(bool flag); Q_INVOKABLE void setLagFlag(bool flag);
Q_INVOKABLE bool emulating(); Q_INVOKABLE bool emulating();
Q_INVOKABLE void registerBefore(const QJSValue& func); Q_INVOKABLE void registerBeforeFrame(const QJSValue& func);
Q_INVOKABLE void registerAfter(const QJSValue& func); Q_INVOKABLE void registerAfterFrame(const QJSValue& func);
Q_INVOKABLE void registerStop(const QJSValue& func); Q_INVOKABLE void registerStop(const QJSValue& func);
Q_INVOKABLE void message(const QString& msg); Q_INVOKABLE void message(const QString& msg);
Q_INVOKABLE void speedMode(const QString& mode); Q_INVOKABLE void speedMode(const QString& mode);
@ -113,6 +115,7 @@ public:
void setDialog(QScriptDialog_t* _dialog){ dialog = _dialog; } void setDialog(QScriptDialog_t* _dialog){ dialog = _dialog; }
void reset(); void reset();
QtScriptInstance* getScript(){ return script; }
QJSValue* getReadFunc(int address) { return readFunc[address]; } QJSValue* getReadFunc(int address) { return readFunc[address]; }
QJSValue* getWriteFunc(int address) { return writeFunc[address]; } QJSValue* getWriteFunc(int address) { return writeFunc[address]; }
QJSValue* getExecFunc(int address) { return execFunc[address]; } QJSValue* getExecFunc(int address) { return execFunc[address]; }
@ -161,6 +164,36 @@ public slots:
}; };
} // JS } // JS
class ScriptExecutionState
{
public:
void start()
{
startTime.readNew();
executing = true;
}
void stop()
{
executing = false;
}
bool isRunning(){ return executing; }
uint64_t timeRunning()
{
FCEU::timeStampRecord now, diff;
now.readNew();
diff = now - startTime;
return diff.toMilliSeconds();
}
private:
bool executing = false;
FCEU::timeStampRecord startTime;
};
class QtScriptInstance : public QObject class QtScriptInstance : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -177,6 +210,9 @@ public:
int call(const QString& funcName, const QJSValueList& args = QJSValueList()); int call(const QString& funcName, const QJSValueList& args = QJSValueList());
void onFrameBegin(); void onFrameBegin();
void onFrameFinish(); void onFrameFinish();
void onGuiUpdate();
void checkForHang();
int runFunc(QJSValue &func, const QJSValueList& args = QJSValueList());
int throwError(QJSValue::ErrorType errorType, const QString &message = QString()); int throwError(QJSValue::ErrorType errorType, const QString &message = QString());
@ -189,6 +225,8 @@ private:
void printSymbols(QJSValue& val, int iter = 0); void printSymbols(QJSValue& val, int iter = 0);
void loadObjectChildren(QJSValue& jsObject, QObject* obj); void loadObjectChildren(QJSValue& jsObject, QObject* obj);
ScriptExecutionState* getExecutionState();
QJSEngine* engine = nullptr; QJSEngine* engine = nullptr;
QScriptDialog_t* dialog = nullptr; QScriptDialog_t* dialog = nullptr;
JS::EmuScriptObject* emu = nullptr; JS::EmuScriptObject* emu = nullptr;
@ -197,19 +235,34 @@ private:
QJSValue *onFrameBeginCallback = nullptr; QJSValue *onFrameBeginCallback = nullptr;
QJSValue *onFrameFinishCallback = nullptr; QJSValue *onFrameFinishCallback = nullptr;
QJSValue *onScriptStopCallback = nullptr; QJSValue *onScriptStopCallback = nullptr;
QJSValue *onGuiUpdateCallback = nullptr;
ScriptExecutionState guiFuncState;
ScriptExecutionState emuFuncState;
bool running = false; bool running = false;
public slots: public slots:
Q_INVOKABLE void print(const QString& msg); Q_INVOKABLE void print(const QString& msg);
Q_INVOKABLE void loadUI(const QString& uiFilePath); Q_INVOKABLE void loadUI(const QString& uiFilePath);
Q_INVOKABLE QString openFileBrowser(const QString& initialPath = QString()); Q_INVOKABLE QString openFileBrowser(const QString& initialPath = QString());
Q_INVOKABLE void registerBefore(const QJSValue& func); Q_INVOKABLE void registerBeforeEmuFrame(const QJSValue& func);
Q_INVOKABLE void registerAfter(const QJSValue& func); Q_INVOKABLE void registerAfterEmuFrame(const QJSValue& func);
Q_INVOKABLE void registerStop(const QJSValue& func); Q_INVOKABLE void registerStop(const QJSValue& func);
Q_INVOKABLE void registerGuiUpdate(const QJSValue& func);
Q_INVOKABLE bool onGuiThread(); Q_INVOKABLE bool onGuiThread();
Q_INVOKABLE bool onEmulationThread(); Q_INVOKABLE bool onEmulationThread();
}; };
class ScriptMonitorThread_t : public QThread
{
Q_OBJECT
protected:
void run( void ) override;
public:
ScriptMonitorThread_t( QObject *parent = 0 );
};
class QtScriptManager : public QObject class QtScriptManager : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -228,12 +281,17 @@ public:
private: private:
static QtScriptManager* _instance; static QtScriptManager* _instance;
FCEU::mutex scriptListMutex;
QList<QtScriptInstance*> scriptList; QList<QtScriptInstance*> scriptList;
FCEU::timeStampRecord lastFrameUpdate; QTimer *periodicUpdateTimer = nullptr;
ScriptMonitorThread_t *monitorThread = nullptr;
friend class ScriptMonitorThread_t;
public slots: public slots:
void frameBeginUpdate(); void frameBeginUpdate();
void frameFinishedUpdate(); void frameFinishedUpdate();
void guiUpdate();
}; };
class JsPropertyItem : public QTreeWidgetItem class JsPropertyItem : public QTreeWidgetItem