diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0437eaff8..908b102a1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,34 +10,80 @@ if (${PUBLIC_RELEASE}) endif() if ( ${QT6} ) - message( STATUS "GUI Frontend: Qt6") - set( Qt Qt6 ) -else() - message( STATUS "GUI Frontend: Qt5") - set( Qt Qt5 ) + set( QT 6 ) endif() -if ( ${QHELP} ) - set(QtHelpModule Help) - add_definitions( -D_USE_QHELP ) +if (NOT DEFINED QT) + message( STATUS "Attempting to determine Qt Version...") + find_package( Qt6 COMPONENTS Core) + + if (${Qt6Core_FOUND}) + message( STATUS "Found Qt Version: ${Qt6Core_VERSION}") + set( QT 6 ) + else() + find_package( Qt5 COMPONENTS Core) + + if (${Qt5Core_FOUND}) + message( STATUS "Found Qt Version: ${Qt5Core_VERSION}") + set( QT 5 ) + endif() + endif() endif() if ( ${FCEU_PROFILER_ENABLE} ) message( STATUS "FCEU Profiler Enabled") add_definitions( -D__FCEU_PROFILER_ENABLE__ ) endif() - -if ( ${QT6} ) - find_package( Qt6 REQUIRED COMPONENTS Widgets OpenGL OpenGLWidgets ${QtHelpModule}) - add_definitions( ${Qt6Widgets_DEFINITIONS} ${Qt6Help_DEFINITIONS} ${Qt6OpenGLWidgets_DEFINITIONS} ) - include_directories( ${Qt6Widgets_INCLUDE_DIRS} ${Qt6Help_INCLUDE_DIRS} ${Qt6OpenGLWidgets_INCLUDE_DIRS} ) +if ( ${QT} EQUAL 6 ) + message( STATUS "GUI Frontend: Qt6") + set( Qt Qt6 ) + find_package( Qt6 REQUIRED COMPONENTS Widgets OpenGL OpenGLWidgets) + find_package( Qt6 COMPONENTS Help QUIET) + find_package( Qt6 COMPONENTS Qml) + add_definitions( ${Qt6Widgets_DEFINITIONS} ${Qt6Qml_DEFINITIONS} ${Qt6Help_DEFINITIONS} ${Qt6OpenGLWidgets_DEFINITIONS} ) + include_directories( ${Qt6Widgets_INCLUDE_DIRS} ${Qt6Qml_INCLUDE_DIRS} ${Qt6Help_INCLUDE_DIRS} ${Qt6OpenGLWidgets_INCLUDE_DIRS} ) + + if (${Qt6Help_FOUND}) + message( STATUS "Qt6 Help Module Found") + if (${QHELP}) + add_definitions( -D_USE_QHELP ) + endif() + else() + message( STATUS "Qt6 Help Module Not Found") + endif() + + if (${Qt6Qml_FOUND}) + message( STATUS "Qt6 Qml Module Found") + #add_definitions( -D__FCEU_QSCRIPT_ENABLE__ ) + else() + message( STATUS "Qt6 Qml Module Not Found") + endif() else() - find_package( Qt5 REQUIRED COMPONENTS Widgets OpenGL ${QtHelpModule}) - add_definitions( ${Qt5Widgets_DEFINITIONS} ${Qt5Help_DEFINITIONS} ) - include_directories( ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Help_INCLUDE_DIRS} ) -endif() + message( STATUS "GUI Frontend: Qt5") + set( Qt Qt5 ) + find_package( Qt5 REQUIRED COMPONENTS Widgets OpenGL) + find_package( Qt5 COMPONENTS Help QUIET) + find_package( Qt5 COMPONENTS Qml) + add_definitions( ${Qt5Widgets_DEFINITIONS} ${Qt5Qml_DEFINITIONS} ${Qt5Help_DEFINITIONS} ) + include_directories( ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Qml_INCLUDE_DIRS} ${Qt5Help_INCLUDE_DIRS} ) + + if (${Qt5Help_FOUND}) + message( STATUS "Qt5 Help Module Found") + if (${QHELP}) + add_definitions( -D_USE_QHELP ) + endif() + else() + message( STATUS "Qt5 Help Module Not Found") + endif() + if (${Qt5Qml_FOUND}) + message( STATUS "Qt5 Qml Module Found") + add_definitions( -D__FCEU_QSCRIPT_ENABLE__ ) + else() + message( STATUS "Qt5 Qml Module Not Found") + endif() +endif() if(WIN32) find_package(OpenGL REQUIRED) @@ -579,6 +625,7 @@ set(SRC_DRIVERS_SDL ${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/ConsoleSoundConf.cpp ${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/StateRecorderConf.cpp ${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/iNesHeaderEditor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/QtScriptManager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/SplashScreen.cpp ${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/TraceLogger.cpp ${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/AboutWindow.cpp @@ -666,6 +713,7 @@ target_link_libraries( ${APP_NAME} ${ASAN_LDFLAGS} ${GPROF_LDFLAGS} ${${Qt}Widgets_LIBRARIES} ${${Qt}Help_LIBRARIES} + ${${Qt}Qml_LIBRARIES} ${${Qt}OpenGL_LIBRARIES} ${${Qt}OpenGLWidgets_LIBRARIES} ${OPENGL_LDFLAGS} diff --git a/src/drivers/Qt/ConsoleWindow.cpp b/src/drivers/Qt/ConsoleWindow.cpp index 2b4ee2ecf..b7030da2e 100644 --- a/src/drivers/Qt/ConsoleWindow.cpp +++ b/src/drivers/Qt/ConsoleWindow.cpp @@ -67,6 +67,7 @@ #include "Qt/main.h" #include "Qt/dface.h" #include "Qt/input.h" +#include "Qt/throttle.h" #include "Qt/ColorMenu.h" #include "Qt/ConsoleWindow.h" #include "Qt/InputConf.h" @@ -86,6 +87,7 @@ #include "Qt/TimingConf.h" #include "Qt/FrameTimingStats.h" #include "Qt/LuaControl.h" +#include "Qt/QtScriptManager.h" #include "Qt/CheatsConf.h" #include "Qt/GameGenie.h" #include "Qt/HexEditor.h" @@ -270,6 +272,9 @@ consoleWin_t::consoleWin_t(QWidget *parent) // Create AVI Recording Disk Thread aviDiskThread = new AviRecordDiskThread_t(this); +#ifdef __FCEU_QSCRIPT_ENABLE__ + QtScriptManager::create(this); +#endif scrHandlerConnected = false; } @@ -1079,7 +1084,7 @@ void consoleWin_t::createMainMenu(void) connect( Hotkeys[ HK_SELECT_STATE_NEXT ].getShortcut(), SIGNAL(activated()), this, SLOT(incrementState(void)) ); #ifdef _S9XLUA_H - // File -> Quick Save + // File -> Load Lua loadLuaAct = new QAction(tr("Load &Lua Script"), this); //loadLuaAct->setShortcut( QKeySequence(tr("F5"))); loadLuaAct->setStatusTip(tr("Load Lua Script")); @@ -1090,7 +1095,20 @@ void consoleWin_t::createMainMenu(void) fileMenu->addSeparator(); #else - loadLuaAct = NULL; + loadLuaAct = nullptr; +#endif + +#ifdef _S9XLUA_H + // File -> Load QScript + loadJsAct = new QAction(tr("Load &Qt Script"), this); + loadJsAct->setStatusTip(tr("Load Qt Script")); + connect(loadJsAct, SIGNAL(triggered()), this, SLOT(loadJs(void)) ); + + fileMenu->addAction(loadJsAct); + + fileMenu->addSeparator(); +#else + loadJsAct = NULL; #endif // File -> Screenshot @@ -2981,6 +2999,19 @@ void consoleWin_t::loadLua(void) #endif } +void consoleWin_t::loadJs(void) +{ +#ifdef __FCEU_QSCRIPT_ENABLE__ + QScriptDialog_t *jsCtrlWin; + + //printf("Open JS Control Window\n"); + + jsCtrlWin = new QScriptDialog_t(this); + + jsCtrlWin->show(); +#endif +} + void consoleWin_t::openInputConfWin(void) { //printf("Open Input Config Window\n"); @@ -4546,21 +4577,30 @@ void consoleWin_t::emuFrameFinish(void) { static bool eventProcessingInProg = false; - if ( eventProcessingInProg ) - { // Prevent recursion as processEvents function can double back on us - return; - } - eventProcessingInProg = true; - // Process all events before attempting to render viewport - QCoreApplication::processEvents(); + guiSignalRecvMark(); - eventProcessingInProg = false; + //if ( eventProcessingInProg ) + //{ // Prevent recursion as processEvents function can double back on us + // return; + //} + // Prevent recursion as processEvents function can double back on us + if ( !eventProcessingInProg ) + { + eventProcessingInProg = true; + // Process all events before attempting to render viewport + QCoreApplication::processEvents(); + eventProcessingInProg = false; + } // Update Input Devices FCEUD_UpdateInput(); //printf("EMU Frame Finish\n"); +#ifdef __FCEU_QSCRIPT_ENABLE__ + QtScriptManager::getInstance()->frameFinishedUpdate(); +#endif + transferVideoBuffer(); } @@ -4569,15 +4609,18 @@ void consoleWin_t::updatePeriodic(void) FCEU_PROFILE_FUNC(prof, "updatePeriodic"); static bool eventProcessingInProg = false; - if ( eventProcessingInProg ) - { // Prevent recursion as processEvents function can double back on us - return; + //if ( eventProcessingInProg ) + //{ // Prevent recursion as processEvents function can double back on us + // return; + //} + // Prevent recursion as processEvents function can double back on us + if ( !eventProcessingInProg ) + { + eventProcessingInProg = true; + // Process all events before attempting to render viewport + QCoreApplication::processEvents(); + eventProcessingInProg = false; } - eventProcessingInProg = true; - // Process all events before attempting to render viewport - QCoreApplication::processEvents(); - - eventProcessingInProg = false; // Update Input Devices FCEUD_UpdateInput(); @@ -4863,6 +4906,7 @@ void emulatorThread_t::run(void) void emulatorThread_t::signalFrameFinished(void) { + emuSignalSendMark(); emit frameFinished(); } diff --git a/src/drivers/Qt/ConsoleWindow.h b/src/drivers/Qt/ConsoleWindow.h index 1fc697497..d0fe59fed 100644 --- a/src/drivers/Qt/ConsoleWindow.h +++ b/src/drivers/Qt/ConsoleWindow.h @@ -206,6 +206,7 @@ class consoleWin_t : public QMainWindow QAction *quickLoadAct; QAction *quickSaveAct; QAction *loadLuaAct; + QAction *loadJsAct; QAction *scrShotAct; QAction *quitAct; QAction *inputConfig; @@ -372,6 +373,7 @@ class consoleWin_t : public QMainWindow void incrementState(void); void decrementState(void); void loadLua(void); + void loadJs(void); void takeScreenShot(void); void prepareScreenShot(void); void powerConsoleCB(void); diff --git a/src/drivers/Qt/FrameTimingStats.cpp b/src/drivers/Qt/FrameTimingStats.cpp index 35878850e..e2551da89 100644 --- a/src/drivers/Qt/FrameTimingStats.cpp +++ b/src/drivers/Qt/FrameTimingStats.cpp @@ -89,6 +89,7 @@ FrameTimingDialog_t::FrameTimingDialog_t(QWidget *parent) frameTimeIdlePct = new QTreeWidgetItem(); frameLateCount = new QTreeWidgetItem(); videoTimeAbs = new QTreeWidgetItem(); + emuSignalDelay = new QTreeWidgetItem(); tree->addTopLevelItem(frameTimeAbs); tree->addTopLevelItem(frameTimeDel); @@ -97,6 +98,7 @@ FrameTimingDialog_t::FrameTimingDialog_t(QWidget *parent) tree->addTopLevelItem(frameTimeWorkPct); tree->addTopLevelItem(frameTimeIdlePct); tree->addTopLevelItem(videoTimeAbs); + tree->addTopLevelItem(emuSignalDelay); tree->addTopLevelItem(frameLateCount); frameTimeAbs->setFlags(Qt::ItemIsEnabled | Qt::ItemNeverHasChildren); @@ -109,6 +111,7 @@ FrameTimingDialog_t::FrameTimingDialog_t(QWidget *parent) frameTimeWorkPct->setText(0, tr("Frame Work %")); frameTimeIdlePct->setText(0, tr("Frame Idle %")); frameLateCount->setText(0, tr("Frame Late Count")); + emuSignalDelay->setText(0, tr("EMU Signal Delay ms")); videoTimeAbs->setText(0, tr("Video Period ms")); frameTimeAbs->setTextAlignment(0, Qt::AlignLeft); @@ -119,6 +122,7 @@ FrameTimingDialog_t::FrameTimingDialog_t(QWidget *parent) frameTimeIdlePct->setTextAlignment(0, Qt::AlignLeft); frameLateCount->setTextAlignment(0, Qt::AlignLeft); videoTimeAbs->setTextAlignment(0, Qt::AlignLeft); + emuSignalDelay->setTextAlignment(0, Qt::AlignLeft); for (int i = 0; i < 4; i++) { @@ -130,6 +134,7 @@ FrameTimingDialog_t::FrameTimingDialog_t(QWidget *parent) frameTimeIdlePct->setTextAlignment(i + 1, Qt::AlignCenter); frameLateCount->setTextAlignment(i + 1, Qt::AlignCenter); videoTimeAbs->setTextAlignment(i + 1, Qt::AlignCenter); + emuSignalDelay->setTextAlignment(i + 1, Qt::AlignCenter); } hbox = new QHBoxLayout(); @@ -294,6 +299,19 @@ void FrameTimingDialog_t::updateTimingStats(void) sprintf(stmp, "%.3f", stats.videoTimeDel.max * 1e3); videoTimeAbs->setText(4, tr(stmp)); + // Emulator to GUI Thread Signal Delay + sprintf(stmp, "%.3f", stats.emuSignalDelay.tgt * 1e3); + emuSignalDelay->setText(1, tr(stmp)); + + sprintf(stmp, "%.3f", stats.emuSignalDelay.cur * 1e3); + emuSignalDelay->setText(2, tr(stmp)); + + sprintf(stmp, "%.3f", stats.emuSignalDelay.min * 1e3); + emuSignalDelay->setText(3, tr(stmp)); + + sprintf(stmp, "%.3f", stats.emuSignalDelay.max * 1e3); + emuSignalDelay->setText(4, tr(stmp)); + // Late Count sprintf(stmp, "%u", stats.lateCount); frameLateCount->setText(1, tr("0")); diff --git a/src/drivers/Qt/FrameTimingStats.h b/src/drivers/Qt/FrameTimingStats.h index 4131430e7..79cae941d 100644 --- a/src/drivers/Qt/FrameTimingStats.h +++ b/src/drivers/Qt/FrameTimingStats.h @@ -40,6 +40,7 @@ class FrameTimingDialog_t : public QDialog QTreeWidgetItem *frameTimeIdlePct; QTreeWidgetItem *frameLateCount; QTreeWidgetItem *videoTimeAbs; + QTreeWidgetItem *emuSignalDelay; QGroupBox *statFrame; QTreeWidget *tree; diff --git a/src/drivers/Qt/QtScriptManager.cpp b/src/drivers/Qt/QtScriptManager.cpp new file mode 100644 index 000000000..888edceab --- /dev/null +++ b/src/drivers/Qt/QtScriptManager.cpp @@ -0,0 +1,653 @@ +/* FCE Ultra - NES/Famicom Emulator + * + * Copyright notice for this file: + * Copyright (C) 2020 thor + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +// QtScriptManager.cpp +// +#ifdef __FCEU_QSCRIPT_ENABLE__ +#include +#include +#include + +#ifdef WIN32 +#include +#endif + +#include +#include +#include +#include +#include + +#include "../../fceu.h" +#include "../../movie.h" + +#include "common/os_utils.h" + +#include "Qt/QtScriptManager.h" +#include "Qt/main.h" +#include "Qt/input.h" +#include "Qt/config.h" +#include "Qt/keyscan.h" +#include "Qt/fceuWrapper.h" +#include "Qt/ConsoleUtilities.h" +#include "Qt/ConsoleWindow.h" + +//---------------------------------------------------- +//---- EMU Script Object +//---------------------------------------------------- +EmuScriptObject::EmuScriptObject(QObject* parent) + : QObject(parent) +{ +} +//---------------------------------------------------- +EmuScriptObject::~EmuScriptObject() +{ +} +//---------------------------------------------------- +void EmuScriptObject::print(const QString& msg) +{ + if (dialog != nullptr) + { + dialog->logOutput(msg); + } +} +//---------------------------------------------------- +void EmuScriptObject::softreset() +{ + fceuWrapperSoftReset(); +} +//---------------------------------------------------- +void EmuScriptObject::pause() +{ + FCEUI_SetEmulationPaused( EMULATIONPAUSED_PAUSED ); +} +//---------------------------------------------------- +void EmuScriptObject::unpause() +{ + FCEUI_SetEmulationPaused(0); +} +//---------------------------------------------------- +//---- Qt Script Instance +//---------------------------------------------------- +QtScriptInstance::QtScriptInstance(QObject* parent) + : QObject(parent) +{ + QScriptDialog_t* win = qobject_cast(parent); + + emu = new EmuScriptObject(this); + + if (win != nullptr) + { + dialog = win; + emu->setDialog(dialog); + } + engine = new QJSEngine(this); + + configEngine(); + + QtScriptManager::getInstance()->addScriptInstance(this); +} +//---------------------------------------------------- +QtScriptInstance::~QtScriptInstance() +{ + if (engine != nullptr) + { + engine->deleteLater(); + engine = nullptr; + } + QtScriptManager::getInstance()->removeScriptInstance(this); + + //printf("QtScriptInstance Destroyed\n"); +} +//---------------------------------------------------- +void QtScriptInstance::resetEngine() +{ + if (engine != nullptr) + { + engine->deleteLater(); + engine = nullptr; + } + engine = new QJSEngine(this); + + configEngine(); +} +//---------------------------------------------------- +int QtScriptInstance::configEngine() +{ + engine->installExtensions(QJSEngine::ConsoleExtension); + + QJSValue emuObject = engine->newQObject(emu); + + engine->globalObject().setProperty("emu", emuObject); + + onFrameFinishCallback = QJSValue(); + + return 0; +} +//---------------------------------------------------- +int QtScriptInstance::loadScriptFile( QString filepath ) +{ + QFile scriptFile(filepath); + + if (!scriptFile.open(QIODevice::ReadOnly)) + { + return -1; + } + QTextStream stream(&scriptFile); + QString fileText = stream.readAll(); + scriptFile.close(); + + FCEU_WRAPPER_LOCK(); + QJSValue evalResult = engine->evaluate(fileText, filepath); + FCEU_WRAPPER_UNLOCK(); + + if (evalResult.isError()) + { + print(evalResult.toString()); + return -1; + } + else + { + //printf("Script Evaluation Success!\n"); + } + onFrameFinishCallback = engine->globalObject().property("onFrameFinish"); + + return 0; +} +//---------------------------------------------------- +void QtScriptInstance::print(const QString& msg) +{ + if (dialog) + { + dialog->logOutput(msg); + } +} +//---------------------------------------------------- +void QtScriptInstance::printSymbols(QJSValue& val, int iter) +{ + int i=0; + if (iter > 10) + { + return; + } + QJSValueIterator it(val); + while (it.hasNext()) + { + it.next(); + QJSValue child = it.value(); + qDebug() << iter << ":" << i << " " << it.name() << ": " << child.toString(); + + bool isPrototype = it.name() == "prototype"; + + if (!isPrototype) + { + printSymbols(child, iter + 1); + } + i++; + } +} +//---------------------------------------------------- +int QtScriptInstance::call(const QString& funcName, const QJSValueList& args) +{ + if (engine == nullptr) + { + return -1; + } + if (!engine->globalObject().hasProperty(funcName)) + { + print(QString("No function exists: ") + funcName); + return -1; + } + QJSValue func = engine->globalObject().property(funcName); + + FCEU_WRAPPER_LOCK(); + QJSValue callResult = func.call(args); + FCEU_WRAPPER_UNLOCK(); + + if (callResult.isError()) + { + print(callResult.toString()); + } + else + { + //printf("Script Call Success!\n"); + } + + QJSValue global = engine->globalObject(); + + printSymbols( global ); + + return 0; +} +//---------------------------------------------------- +void QtScriptInstance::onFrameFinish() +{ + if (onFrameFinishCallback.isCallable()) + { + onFrameFinishCallback.call(); + } +} +//---------------------------------------------------- +bool QtScriptInstance::isRunning() +{ + return false; +} +//---------------------------------------------------- +//---- Qt Script Manager +//---------------------------------------------------- +QtScriptManager* QtScriptManager::_instance = nullptr; + +QtScriptManager::QtScriptManager(QObject* parent) + : QObject(parent) +{ + _instance = this; +} +//---------------------------------------------------- +QtScriptManager::~QtScriptManager() +{ + _instance = nullptr; + //printf("QtScriptManager destroyed\n"); +} +//---------------------------------------------------- +QtScriptManager* QtScriptManager::create(QObject* parent) +{ + QtScriptManager* mgr = new QtScriptManager(parent); + + //printf("QtScriptManager created\n"); + + return mgr; +} +//---------------------------------------------------- +void QtScriptManager::addScriptInstance(QtScriptInstance* script) +{ + scriptList.push_back(script); +} +//---------------------------------------------------- +void QtScriptManager::removeScriptInstance(QtScriptInstance* script) +{ + auto it = scriptList.begin(); + + while (it != scriptList.end()) + { + if (*it == script) + { + it = scriptList.erase(it); + } + else + { + it++; + } + } +} +//---------------------------------------------------- +void QtScriptManager::frameFinishedUpdate() +{ + FCEU_WRAPPER_LOCK(); + for (auto script : scriptList) + { + script->onFrameFinish(); + } + FCEU_WRAPPER_UNLOCK(); +} +//---------------------------------------------------- +//---- Qt Script Dialog Window +//---------------------------------------------------- +QScriptDialog_t::QScriptDialog_t(QWidget *parent) + : QDialog(parent, Qt::Window) +{ + QVBoxLayout *mainLayout; + QHBoxLayout *hbox; + QPushButton *closeButton; + QLabel *lbl; + std::string filename; + QSettings settings; + + resize(512, 512); + + setWindowTitle(tr("Qt Java Script Control")); + + mainLayout = new QVBoxLayout(); + + lbl = new QLabel(tr("Script File:")); + + scriptPath = new QLineEdit(); + scriptArgs = new QLineEdit(); + + g_config->getOption("SDL.LastLoadJs", &filename); + + scriptPath->setText( tr(filename.c_str()) ); + scriptPath->setClearButtonEnabled(true); + scriptArgs->setClearButtonEnabled(true); + + jsOutput = new QTextEdit(); + jsOutput->setReadOnly(true); + + hbox = new QHBoxLayout(); + + browseButton = new QPushButton(tr("Browse")); + stopButton = new QPushButton(tr("Stop")); + + scriptInstance = new QtScriptInstance(this); + + if (scriptInstance->isRunning()) + { + startButton = new QPushButton(tr("Restart")); + } + else + { + startButton = new QPushButton(tr("Start")); + } + + stopButton->setEnabled(scriptInstance->isRunning()); + + connect(browseButton, SIGNAL(clicked()), this, SLOT(openScriptFile(void))); + connect(stopButton, SIGNAL(clicked()), this, SLOT(stopScript(void))); + connect(startButton, SIGNAL(clicked()), this, SLOT(startScript(void))); + + hbox->addWidget(browseButton); + hbox->addWidget(stopButton); + hbox->addWidget(startButton); + + mainLayout->addWidget(lbl); + mainLayout->addWidget(scriptPath); + mainLayout->addLayout(hbox); + + hbox = new QHBoxLayout(); + lbl = new QLabel(tr("Arguments:")); + + hbox->addWidget(lbl); + hbox->addWidget(scriptArgs); + + mainLayout->addLayout(hbox); + + lbl = new QLabel(tr("Output Console:")); + mainLayout->addWidget(lbl); + mainLayout->addWidget(jsOutput); + + closeButton = new QPushButton( tr("Close") ); + closeButton->setIcon(style()->standardIcon(QStyle::SP_DialogCloseButton)); + connect(closeButton, SIGNAL(clicked(void)), this, SLOT(closeWindow(void))); + + hbox = new QHBoxLayout(); + hbox->addStretch(5); + hbox->addWidget( closeButton, 1 ); + mainLayout->addLayout( hbox ); + + setLayout(mainLayout); + + //winList.push_back(this); + + periodicTimer = new QTimer(this); + + connect(periodicTimer, &QTimer::timeout, this, &QScriptDialog_t::updatePeriodic); + + periodicTimer->start(200); // 5hz + + restoreGeometry(settings.value("QScriptWindow/geometry").toByteArray()); +} + +//---------------------------------------------------- +QScriptDialog_t::~QScriptDialog_t(void) +{ + QSettings settings; + std::list::iterator it; + + //printf("Destroy JS Control Window\n"); + + periodicTimer->stop(); + + //for (it = winList.begin(); it != winList.end(); it++) + //{ + // if ((*it) == this) + // { + // winList.erase(it); + // //printf("Removing JS Window\n"); + // break; + // } + //} + settings.setValue("QScriptWindow/geometry", saveGeometry()); +} +//---------------------------------------------------- +void QScriptDialog_t::closeEvent(QCloseEvent *event) +{ + //printf("JS Control Close Window Event\n"); + done(0); + deleteLater(); + event->accept(); +} +//---------------------------------------------------- +void QScriptDialog_t::closeWindow(void) +{ + //printf("JS Control Close Window\n"); + done(0); + deleteLater(); +} +//---------------------------------------------------- +void QScriptDialog_t::updatePeriodic(void) +{ + // TODO + //printf("Update JS\n"); + //if (updateJSDisplay) + //{ + // updateJSWindows(); + // updateJSDisplay = false; + //} +} +//---------------------------------------------------- +void QScriptDialog_t::openJSKillMessageBox(void) +{ + int ret; + QMessageBox msgBox(this); + + msgBox.setIcon(QMessageBox::Warning); + msgBox.setText(tr("The JS script running has been running a long time.\nIt may have gone crazy. Kill it? (I won't ask again if you say No)\n")); + msgBox.setStandardButtons(QMessageBox::Yes); + msgBox.addButton(QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::No); + + ret = msgBox.exec(); + + if (ret == QMessageBox::Yes) + { + } +} +//---------------------------------------------------- +void QScriptDialog_t::openScriptFile(void) +{ + int ret, useNativeFileDialogVal; + QString filename; + std::string last; + std::string dir; + const char *exePath = nullptr; + const char *jsPath = nullptr; + QFileDialog dialog(this, tr("Open JS Script")); + QList urls; + QDir d; + + exePath = fceuExecutablePath(); + + //urls = dialog.sidebarUrls(); + urls << QUrl::fromLocalFile(QDir::rootPath()); + urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::HomeLocation).first()); + urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::DesktopLocation).first()); + urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::DownloadLocation).first()); + urls << QUrl::fromLocalFile(QDir(FCEUI_GetBaseDirectory()).absolutePath()); + + if (exePath[0] != 0) + { + d.setPath(QString(exePath) + "/../jsScripts"); + + if (d.exists()) + { + urls << QUrl::fromLocalFile(d.absolutePath()); + } + } +#ifndef WIN32 + d.setPath("/usr/share/fceux/jsScripts"); + + if (d.exists()) + { + urls << QUrl::fromLocalFile(d.absolutePath()); + } +#endif + + jsPath = getenv("FCEU_QSCRIPT_PATH"); + + // Parse LUA_PATH and add to urls + if (jsPath) + { + int i, j; + char stmp[2048]; + + i = j = 0; + while (jsPath[i] != 0) + { + if (jsPath[i] == ';') + { + stmp[j] = 0; + + if (j > 0) + { + d.setPath(stmp); + + if (d.exists()) + { + urls << QUrl::fromLocalFile(d.absolutePath()); + } + } + j = 0; + } + else + { + stmp[j] = jsPath[i]; + j++; + } + i++; + } + + stmp[j] = 0; + + if (j > 0) + { + d.setPath(stmp); + + if (d.exists()) + { + urls << QUrl::fromLocalFile(d.absolutePath()); + } + } + } + + dialog.setFileMode(QFileDialog::ExistingFile); + + dialog.setNameFilter(tr("JS Scripts (*.js *.JS) ;; All files (*)")); + + dialog.setViewMode(QFileDialog::List); + dialog.setFilter(QDir::AllEntries | QDir::AllDirs | QDir::Hidden); + dialog.setLabelText(QFileDialog::Accept, tr("Load")); + + g_config->getOption("SDL.LastLoadJs", &last); + + if (last.size() == 0) + { +#ifdef WIN32 + last.assign(FCEUI_GetBaseDirectory()); +#else + last.assign("/usr/share/fceux/jsScripts"); +#endif + } + + getDirFromFile(last.c_str(), dir); + + dialog.setDirectory(tr(dir.c_str())); + + // Check config option to use native file dialog or not + g_config->getOption("SDL.UseNativeFileDialog", &useNativeFileDialogVal); + + dialog.setOption(QFileDialog::DontUseNativeDialog, !useNativeFileDialogVal); + dialog.setSidebarUrls(urls); + + ret = dialog.exec(); + + if (ret) + { + QStringList fileList; + fileList = dialog.selectedFiles(); + + if (fileList.size() > 0) + { + filename = fileList[0]; + } + } + + if (filename.isNull()) + { + return; + } + qDebug() << "selected file path : " << filename.toUtf8(); + + g_config->setOption("SDL.LastLoadJs", filename.toStdString().c_str()); + + scriptPath->setText(filename); + +} +//---------------------------------------------------- +void QScriptDialog_t::startScript(void) +{ + scriptInstance->resetEngine(); + if (scriptInstance->loadScriptFile(scriptPath->text())) + { + // Script parsing error + return; + } + // TODO add option to pass options to script main. + QJSValue argArray = scriptInstance->getEngine()->newArray(4); + argArray.setProperty(0, "arg1"); + argArray.setProperty(1, "arg2"); + argArray.setProperty(2, "arg3"); + + QJSValueList argList = { argArray }; + + scriptInstance->call("main", argList); +} +//---------------------------------------------------- +void QScriptDialog_t::stopScript(void) +{ +} +//---------------------------------------------------- +void QScriptDialog_t::refreshState(void) +{ + if (scriptInstance->isRunning()) + { + stopButton->setEnabled(true); + startButton->setText(tr("Restart")); + } + else + { + stopButton->setEnabled(false); + startButton->setText(tr("Start")); + } +} +//---------------------------------------------------- +void QScriptDialog_t::logOutput(const QString& text) +{ + jsOutput->insertPlainText(text); +} +//---------------------------------------------------- +#endif // __FCEU_QSCRIPT_ENABLE__ diff --git a/src/drivers/Qt/QtScriptManager.h b/src/drivers/Qt/QtScriptManager.h new file mode 100644 index 000000000..2844fcb3d --- /dev/null +++ b/src/drivers/Qt/QtScriptManager.h @@ -0,0 +1,144 @@ +// QtScriptManager.h +// + +#pragma once + +#ifdef __FCEU_QSCRIPT_ENABLE__ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Qt/main.h" +#include "utils/timeStamp.h" + +class QScriptDialog_t; + +class EmuScriptObject: public QObject +{ + Q_OBJECT +public: + EmuScriptObject(QObject* parent = nullptr); + ~EmuScriptObject(); + + void setDialog(QScriptDialog_t* _dialog){ dialog = _dialog; } + +private: + QScriptDialog_t* dialog = nullptr; + +public slots: + Q_INVOKABLE void print(const QString& msg); + Q_INVOKABLE void softreset(); + Q_INVOKABLE void pause(); + Q_INVOKABLE void unpause(); + +}; + +class QtScriptInstance : public QObject +{ + Q_OBJECT +public: + QtScriptInstance(QObject* parent = nullptr); + ~QtScriptInstance(); + + void resetEngine(); + int loadScriptFile(QString filepath); + + bool isRunning(); + + int call(const QString& funcName, const QJSValueList& args = QJSValueList()); + void onFrameFinish(); + + QJSEngine* getEngine(){ return engine; }; +private: + + int configEngine(); + void printSymbols(QJSValue& val, int iter = 0); + + QJSEngine* engine = nullptr; + QScriptDialog_t* dialog = nullptr; + EmuScriptObject* emu = nullptr; + QJSValue onFrameFinishCallback; + +public slots: + Q_INVOKABLE void print(const QString& msg); +}; + +class QtScriptManager : public QObject +{ + Q_OBJECT + +public: + QtScriptManager(QObject* parent = nullptr); + ~QtScriptManager(); + + static QtScriptManager* getInstance(){ return _instance; } + static QtScriptManager* create(QObject* parent = nullptr); + + void addScriptInstance(QtScriptInstance* script); + void removeScriptInstance(QtScriptInstance* script); +private: + static QtScriptManager* _instance; + + QList scriptList; + FCEU::timeStampRecord lastFrameUpdate; + +public slots: + void frameFinishedUpdate(); +}; + +class QScriptDialog_t : public QDialog +{ + Q_OBJECT + +public: + QScriptDialog_t(QWidget *parent = nullptr); + ~QScriptDialog_t(void); + + void refreshState(void); + void logOutput(const QString& text); + +protected: + void closeEvent(QCloseEvent *bar); + void openJSKillMessageBox(void); + + QTimer *periodicTimer; + QLineEdit *scriptPath; + QLineEdit *scriptArgs; + QPushButton *browseButton; + QPushButton *stopButton; + QPushButton *startButton; + QTextEdit *jsOutput; + QtScriptInstance *scriptInstance; + +private: +public slots: + void closeWindow(void); +private slots: + void updatePeriodic(void); + void openScriptFile(void); + void startScript(void); + void stopScript(void); +}; + +// Formatted print +//int LuaPrintfToWindowConsole( __FCEU_PRINTF_FORMAT const char *format, ...) __FCEU_PRINTF_ATTRIBUTE( 1, 2 ); + +//void PrintToWindowConsole(intptr_t hDlgAsInt, const char *str); + +//int LuaKillMessageBox(void); + +#endif // __FCEU_QSCRIPT_ENABLE__ diff --git a/src/drivers/Qt/config.cpp b/src/drivers/Qt/config.cpp index ded54a16b..50442ffb6 100644 --- a/src/drivers/Qt/config.cpp +++ b/src/drivers/Qt/config.cpp @@ -801,6 +801,7 @@ InitConfig() config->addOption("_lastsavestateas", "SDL.LastSaveStateAs", savPath ); config->addOption("_lastopenmovie", "SDL.LastOpenMovie", movPath); config->addOption("_lastloadlua", "SDL.LastLoadLua", ""); + config->addOption("_lastloadjs", "SDL.LastLoadJs", ""); config->addOption("SDL.HelpFilePath", ""); config->addOption("SDL.AviFilePath", ""); config->addOption("SDL.WavFilePath", ""); diff --git a/src/drivers/Qt/sdl-throttle.cpp b/src/drivers/Qt/sdl-throttle.cpp index 800ad121d..8d5daece4 100644 --- a/src/drivers/Qt/sdl-throttle.cpp +++ b/src/drivers/Qt/sdl-throttle.cpp @@ -39,6 +39,7 @@ static const double Normal = 1.0; // 1x speed (around 60 fps on NTSC) static uint32 frameLateCounter = 0; static FCEU::timeStampRecord Lasttime, Nexttime, Latetime; static FCEU::timeStampRecord DesiredFrameTime, HalfFrameTime, QuarterFrameTime, DoubleFrameTime; +static FCEU::timeStampRecord emuSignalTx, guiSignalRx, emuSignalLatency; static double desired_frametime = (1.0 / 60.099823); static double desired_frameRate = (60.099823); static double baseframeRate = (60.099823); @@ -52,6 +53,9 @@ static double videoLastTs = 0.0; static double videoPeriodCur = 0.0; static double videoPeriodMin = 1.0; static double videoPeriodMax = 0.0; +static double emuLatencyCur = 0.0; +static double emuLatencyMin = 1.0; +static double emuLatencyMax = 0.0; static bool keepFrameTimeStats = false; static int InFrame = 0; double g_fpsScale = Normal; // used by sdl.cpp @@ -193,6 +197,11 @@ int getFrameTimingStats( struct frameTimingStat_t *stats ) stats->videoTimeDel.min = videoPeriodMin; stats->videoTimeDel.max = videoPeriodMax; + stats->emuSignalDelay.tgt = 0.0; + stats->emuSignalDelay.cur = emuLatencyCur; + stats->emuSignalDelay.min = emuLatencyMin; + stats->emuSignalDelay.max = emuLatencyMax; + return 0; } @@ -216,6 +225,34 @@ void videoBufferSwapMark(void) } } +void emuSignalSendMark(void) +{ + if ( keepFrameTimeStats ) + { + emuSignalTx.readNew(); + } +} + +void guiSignalRecvMark(void) +{ + if ( keepFrameTimeStats ) + { + guiSignalRx.readNew(); + emuSignalLatency = guiSignalRx - emuSignalTx; + + emuLatencyCur = emuSignalLatency.toSeconds(); + + if ( emuLatencyCur < emuLatencyMin ) + { + emuLatencyMin = emuLatencyCur; + } + if ( emuLatencyCur > emuLatencyMax ) + { + emuLatencyMax = emuLatencyCur; + } + } +} + void resetFrameTiming(void) { frameLateCounter = 0; @@ -225,6 +262,8 @@ void resetFrameTiming(void) frameIdleMin = 1.0; videoPeriodMin = 1.0; videoPeriodMax = 0.0; + emuLatencyMin = 1.0; + emuLatencyMax = 0.0; } /* LOGMUL = exp(log(2) / 3) diff --git a/src/drivers/Qt/throttle.h b/src/drivers/Qt/throttle.h index 7cffdbe65..64f4baa41 100644 --- a/src/drivers/Qt/throttle.h +++ b/src/drivers/Qt/throttle.h @@ -43,6 +43,13 @@ struct frameTimingStat_t double max; } videoTimeDel; + struct { + double tgt; + double cur; + double min; + double max; + } emuSignalDelay; + unsigned int lateCount; bool enabled; @@ -52,6 +59,8 @@ void resetFrameTiming(void); void setFrameTimingEnable( bool enable ); int getFrameTimingStats( struct frameTimingStat_t *stats ); void videoBufferSwapMark(void); +void emuSignalSendMark(void); +void guiSignalRecvMark(void); double getHighPrecTimeStamp(void); double getFrameRate(void); double getFrameRateAdjustmentRatio(void);