fceux/src/drivers/Qt/QtScriptManager.h

590 lines
16 KiB
C++

// QtScriptManager.h
//
#pragma once
#ifdef __FCEU_QSCRIPT_ENABLE__
#include <stdio.h>
#include <stdarg.h>
#include <QColor>
#include <QWidget>
#include <QDialog>
#include <QTabWidget>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QComboBox>
#include <QCheckBox>
#include <QPushButton>
#include <QLabel>
#include <QFrame>
#include <QGroupBox>
#include <QLineEdit>
#include <QTextEdit>
#include <QTreeView>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QJSEngine>
#include <QThread>
#include "Qt/main.h"
#include "utils/mutex.h"
#include "utils/timeStamp.h"
class QScriptDialog_t;
class QtScriptInstance;
namespace FCEU
{
class JSEngine : public QJSEngine
{
Q_OBJECT
public:
JSEngine(QObject* parent = nullptr);
~JSEngine();
QScriptDialog_t* getDialog(){ return dialog; }
QtScriptInstance* getScript(){ return script; }
void setDialog(QScriptDialog_t* _dialog){ dialog = _dialog; }
void setScript(QtScriptInstance* _script){ script = _script; }
enum logLevel
{
FATAL = 0,
CRITICAL,
WARNING,
INFO,
DEBUG,
};
void acquireThreadContext();
void releaseThreadContext();
void setLogLevel(enum logLevel lvl){ _logLevel = lvl; }
void logMessage(int lvl, const QString& msg);
static JSEngine* getCurrent();
private:
QScriptDialog_t* dialog = nullptr;
QtScriptInstance* script = nullptr;
int _logLevel = WARNING;
JSEngine* prevContext = nullptr;
};
} // FCEU
namespace JS
{
class ColorScriptObject: public QObject
{
Q_OBJECT
Q_PROPERTY(int red READ getRed WRITE setRed)
Q_PROPERTY(int green READ getGreen WRITE setGreen)
Q_PROPERTY(int blue READ getBlue WRITE setBlue)
Q_PROPERTY(int palette READ getPalette WRITE setPalette)
public:
Q_INVOKABLE ColorScriptObject(int r=0, int g=0, int b=0);
~ColorScriptObject();
private:
QColor color;
int _palette;
static int numInstances;
public slots:
Q_INVOKABLE int getRed(){ return color.red(); }
Q_INVOKABLE int getGreen(){ return color.green(); }
Q_INVOKABLE int getBlue(){ return color.blue(); }
Q_INVOKABLE void setRed(int r){ color.setRed(r); }
Q_INVOKABLE void setGreen(int g){ color.setGreen(g); }
Q_INVOKABLE void setBlue(int b){ color.setBlue(b); }
Q_INVOKABLE int getPalette(){ return _palette; }
Q_INVOKABLE void setPalette(int p){ _palette = p; }
};
class JoypadScriptObject: public QObject
{
Q_OBJECT
Q_PROPERTY(bool up READ getUp)
Q_PROPERTY(bool down READ getDown)
Q_PROPERTY(bool left READ getLeft)
Q_PROPERTY(bool right READ getRight)
Q_PROPERTY(bool select READ getSelect)
Q_PROPERTY(bool start READ getStart)
Q_PROPERTY(bool a READ getA)
Q_PROPERTY(bool b READ getB)
Q_PROPERTY(int player READ getPlayer)
public:
JoypadScriptObject(int playerIdx);
~JoypadScriptObject();
void readData();
void readDataPhy();
private:
bool up = false;
bool down = false;
bool left = false;
bool right = false;
bool select = false;
bool start = false;
bool a = false;
bool b = false;
int player = 0;
static int numInstances;
public slots:
Q_INVOKABLE bool getUp(){ return up; }
Q_INVOKABLE bool getDown(){ return down; }
Q_INVOKABLE bool getLeft(){ return left; }
Q_INVOKABLE bool getRight(){ return right; }
Q_INVOKABLE bool getSelect(){ return select; }
Q_INVOKABLE bool getStart(){ return start; }
Q_INVOKABLE bool getA(){ return a; }
Q_INVOKABLE bool getB(){ return b; }
Q_INVOKABLE int getPlayer(){ return player; }
};
class EmuStateScriptObject: public QObject
{
Q_OBJECT
public:
Q_INVOKABLE EmuStateScriptObject(const QJSValue& jsArg1 = QJSValue(), const QJSValue& jsArg2 = QJSValue());
~EmuStateScriptObject();
Q_PROPERTY(bool persist READ isPersistent WRITE setPersistent)
Q_PROPERTY(int slot READ getSlot WRITE setSlot)
Q_PROPERTY(int compressionLevel READ getCompressionLevel WRITE setCompressionLevel)
EmuStateScriptObject& operator= (const EmuStateScriptObject& obj);
private:
QString filename;
EMUFILE_MEMORY *data = nullptr;
int compression = 0;
int slot = -1;
bool persist = false;
static int numInstances;
void logMessage(int lvl, QString& msg);
public slots:
Q_INVOKABLE bool save();
Q_INVOKABLE bool load();
Q_INVOKABLE bool isValid(){ return (data != nullptr); }
Q_INVOKABLE bool isPersistent(){ return persist; }
Q_INVOKABLE void setPersistent(bool value){ persist = value; }
Q_INVOKABLE int getSlot(){ return slot; }
Q_INVOKABLE void setSlot(int value);
Q_INVOKABLE int getCompressionLevel(){ return compression; }
Q_INVOKABLE void setCompressionLevel(int value){ compression = value; }
Q_INVOKABLE void setFilename(const QString& name){ filename = name; }
Q_INVOKABLE bool saveToFile(const QString& filepath);
Q_INVOKABLE bool loadFromFile(const QString& filepath);
Q_INVOKABLE bool saveFileExists();
Q_INVOKABLE QJSValue copy();
};
class EmuScriptObject: public QObject
{
Q_OBJECT
public:
EmuScriptObject(QObject* parent = nullptr);
~EmuScriptObject();
void setEngine(FCEU::JSEngine* _engine){ engine = _engine; }
void setDialog(QScriptDialog_t* _dialog){ dialog = _dialog; }
private:
FCEU::JSEngine* engine = nullptr;
QScriptDialog_t* dialog = nullptr;
QtScriptInstance* script = nullptr;
public slots:
Q_INVOKABLE void print(const QString& msg);
Q_INVOKABLE void powerOn();
Q_INVOKABLE void softReset();
Q_INVOKABLE void pause();
Q_INVOKABLE void unpause();
Q_INVOKABLE bool paused();
Q_INVOKABLE void frameAdvance();
Q_INVOKABLE int frameCount();
Q_INVOKABLE int lagCount();
Q_INVOKABLE bool lagged();
Q_INVOKABLE void setLagFlag(bool flag);
Q_INVOKABLE bool emulating();
Q_INVOKABLE bool isReadOnly();
Q_INVOKABLE void setReadOnly(bool flag);
Q_INVOKABLE void setRenderPlanes(bool sprites, bool background);
Q_INVOKABLE void registerBeforeFrame(const QJSValue& func);
Q_INVOKABLE void registerAfterFrame(const QJSValue& func);
Q_INVOKABLE void registerStop(const QJSValue& func);
Q_INVOKABLE void message(const QString& msg);
Q_INVOKABLE void speedMode(const QString& mode);
Q_INVOKABLE bool loadRom(const QString& romPath);
Q_INVOKABLE bool onEmulationThread();
Q_INVOKABLE bool addGameGenie(const QString& code);
Q_INVOKABLE bool delGameGenie(const QString& code);
Q_INVOKABLE void exit();
Q_INVOKABLE QString getDir();
Q_INVOKABLE QJSValue getScreenPixel(int x, int y, bool useBackup = false);
Q_INVOKABLE QJSValue createState(int slot = -1);
};
class RomScriptObject: public QObject
{
Q_OBJECT
public:
RomScriptObject(QObject* parent = nullptr);
~RomScriptObject();
void setEngine(FCEU::JSEngine* _engine){ engine = _engine; }
void setDialog(QScriptDialog_t* _dialog){ dialog = _dialog; }
private:
FCEU::JSEngine* engine = nullptr;
QScriptDialog_t* dialog = nullptr;
QtScriptInstance* script = nullptr;
public slots:
Q_INVOKABLE bool isLoaded();
Q_INVOKABLE QString getFileName();
Q_INVOKABLE QString getHash(const QString& type);
Q_INVOKABLE uint8_t readByte(int address);
Q_INVOKABLE int8_t readByteSigned(int address);
Q_INVOKABLE uint8_t readByteUnsigned(int address);
Q_INVOKABLE QJSValue readByteRange(int start, int end);
Q_INVOKABLE void writeByte(int address, int value);
};
class MemoryScriptObject: public QObject
{
Q_OBJECT
public:
MemoryScriptObject(QObject* parent = nullptr);
~MemoryScriptObject();
void setEngine(FCEU::JSEngine* _engine){ engine = _engine; }
void setDialog(QScriptDialog_t* _dialog){ dialog = _dialog; }
void reset();
QtScriptInstance* getScript(){ return script; }
QJSValue* getReadFunc(int address) { return readFunc[address]; }
QJSValue* getWriteFunc(int address) { return writeFunc[address]; }
QJSValue* getExecFunc(int address) { return execFunc[address]; }
private:
static constexpr int AddressRange = 0x10000;
FCEU::JSEngine* engine = nullptr;
QScriptDialog_t* dialog = nullptr;
QtScriptInstance* script = nullptr;
QJSValue* readFunc[AddressRange] = { nullptr };
QJSValue* writeFunc[AddressRange] = { nullptr };
QJSValue* execFunc[AddressRange] = { nullptr };
int numReadFuncsRegistered = 0;
int numWriteFuncsRegistered = 0;
int numExecFuncsRegistered = 0;
void registerCallback(int type, const QJSValue& func, int address, int size = 1);
void unregisterCallback(int type, const QJSValue& func, int address, int size = 1);
public slots:
Q_INVOKABLE uint8_t readByte(int address);
Q_INVOKABLE int8_t readByteSigned(int address);
Q_INVOKABLE uint8_t readByteUnsigned(int address);
Q_INVOKABLE uint16_t readWord(int addressLow, int addressHigh = -1);
Q_INVOKABLE int16_t readWordSigned(int addressLow, int addressHigh = -1);
Q_INVOKABLE uint16_t readWordUnsigned(int addressLow, int addressHigh = -1);
Q_INVOKABLE void writeByte(int address, int value);
Q_INVOKABLE uint16_t getRegisterPC();
Q_INVOKABLE uint8_t getRegisterA();
Q_INVOKABLE uint8_t getRegisterX();
Q_INVOKABLE uint8_t getRegisterY();
Q_INVOKABLE uint8_t getRegisterS();
Q_INVOKABLE uint8_t getRegisterP();
Q_INVOKABLE void setRegisterPC(uint16_t v);
Q_INVOKABLE void setRegisterA(uint8_t v);
Q_INVOKABLE void setRegisterX(uint8_t v);
Q_INVOKABLE void setRegisterY(uint8_t v);
Q_INVOKABLE void setRegisterS(uint8_t v);
Q_INVOKABLE void setRegisterP(uint8_t v);
Q_INVOKABLE void registerRead(const QJSValue& func, int address, int size = 1);
Q_INVOKABLE void registerWrite(const QJSValue& func, int address, int size = 1);
Q_INVOKABLE void registerExec(const QJSValue& func, int address, int size = 1);
Q_INVOKABLE void unregisterRead(const QJSValue& func, int address, int size = 1);
Q_INVOKABLE void unregisterWrite(const QJSValue& func, int address, int size = 1);
Q_INVOKABLE void unregisterExec(const QJSValue& func, int address, int size = 1);
Q_INVOKABLE void unregisterAll();
};
class PpuScriptObject: public QObject
{
Q_OBJECT
public:
PpuScriptObject(QObject* parent = nullptr);
~PpuScriptObject();
void setEngine(FCEU::JSEngine* _engine){ engine = _engine; }
void setDialog(QScriptDialog_t* _dialog){ dialog = _dialog; }
private:
FCEU::JSEngine* engine = nullptr;
QScriptDialog_t* dialog = nullptr;
QtScriptInstance* script = nullptr;
public slots:
Q_INVOKABLE uint8_t readByte(int address);
Q_INVOKABLE int8_t readByteSigned(int address);
Q_INVOKABLE uint8_t readByteUnsigned(int address);
Q_INVOKABLE QJSValue readByteRange(int start, int end);
Q_INVOKABLE void writeByte(int address, int value);
};
class InputScriptObject: public QObject
{
Q_OBJECT
public:
InputScriptObject(QObject* parent = nullptr);
~InputScriptObject();
void setEngine(FCEU::JSEngine* _engine){ engine = _engine; }
void setDialog(QScriptDialog_t* _dialog){ dialog = _dialog; }
static constexpr int MAX_NUM_JOYPADS = 4;
private:
FCEU::JSEngine* engine = nullptr;
QScriptDialog_t* dialog = nullptr;
QtScriptInstance* script = nullptr;
struct
{
JoypadScriptObject* qObj;
QJSValue jsObj;
} joypad[MAX_NUM_JOYPADS];
public slots:
Q_INVOKABLE QJSValue readJoypad(int player, bool immediate = false);
};
} // JS
class ScriptExecutionState
{
public:
void start()
{
timeMs = 0;
executing = true;
}
void stop()
{
executing = false;
timeMs = 0;
}
bool isRunning(){ return executing; }
unsigned int timeCheck()
{
unsigned int retval = timeMs;
timeMs += checkPeriod;
return retval;
}
static constexpr unsigned int checkPeriod = 100;
private:
bool executing = false;
unsigned int timeMs = 0;
};
class QtScriptInstance : public QObject
{
Q_OBJECT
public:
QtScriptInstance(QObject* parent = nullptr);
~QtScriptInstance();
void resetEngine();
int loadScriptFile(QString filepath);
bool isRunning(){ return running; };
void stopRunning();
int call(const QString& funcName, const QJSValueList& args = QJSValueList());
void frameAdvance();
void onFrameBegin();
void onFrameFinish();
void onGuiUpdate();
void checkForHang();
int runFunc(QJSValue &func, const QJSValueList& args = QJSValueList());
int throwError(QJSValue::ErrorType errorType, const QString &message = QString());
FCEU::JSEngine* getEngine(){ return engine; };
private:
int initEngine();
void shutdownEngine();
void printSymbols(QJSValue& val, int iter = 0);
void loadObjectChildren(QJSValue& jsObject, QObject* obj);
ScriptExecutionState* getExecutionState();
FCEU::JSEngine* engine = nullptr;
QScriptDialog_t* dialog = nullptr;
JS::EmuScriptObject* emu = nullptr;
JS::RomScriptObject* rom = nullptr;
JS::PpuScriptObject* ppu = nullptr;
JS::MemoryScriptObject* mem = nullptr;
JS::InputScriptObject* input = nullptr;
QWidget* ui_rootWidget = nullptr;
QJSValue *onFrameBeginCallback = nullptr;
QJSValue *onFrameFinishCallback = nullptr;
QJSValue *onScriptStopCallback = nullptr;
QJSValue *onGuiUpdateCallback = nullptr;
ScriptExecutionState guiFuncState;
ScriptExecutionState emuFuncState;
int frameAdvanceCount = 0;
int frameAdvanceState = 0;
bool running = false;
public slots:
Q_INVOKABLE void print(const QString& msg);
Q_INVOKABLE void loadUI(const QString& uiFilePath);
Q_INVOKABLE QString openFileBrowser(const QString& initialPath = QString());
Q_INVOKABLE void registerBeforeEmuFrame(const QJSValue& func);
Q_INVOKABLE void registerAfterEmuFrame(const QJSValue& func);
Q_INVOKABLE void registerStop(const QJSValue& func);
Q_INVOKABLE void registerGuiUpdate(const QJSValue& func);
Q_INVOKABLE bool onGuiThread();
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
{
Q_OBJECT
public:
QtScriptManager(QObject* parent = nullptr);
~QtScriptManager();
static QtScriptManager* getInstance(){ return _instance; }
static QtScriptManager* create(QObject* parent = nullptr);
static void destroy();
static void logMessageQt(QtMsgType type, const QString &msg);
int numScriptsLoaded(void){ return scriptList.size(); }
void addScriptInstance(QtScriptInstance* script);
void removeScriptInstance(QtScriptInstance* script);
private:
static QtScriptManager* _instance;
FCEU::mutex scriptListMutex;
QList<QtScriptInstance*> scriptList;
QTimer *periodicUpdateTimer = nullptr;
ScriptMonitorThread_t *monitorThread = nullptr;
friend class ScriptMonitorThread_t;
public slots:
void frameBeginUpdate();
void frameFinishedUpdate();
void guiUpdate();
};
class JsPropertyItem : public QTreeWidgetItem
{
public:
JsPropertyItem()
: QTreeWidgetItem()
{
}
virtual ~JsPropertyItem() override
{
}
QJSValue jsValue;
QMap<QString, JsPropertyItem*> childMap;
};
class JsPropertyTree : public QTreeWidget
{
Q_OBJECT
public:
JsPropertyTree(QWidget *parent = nullptr)
: QTreeWidget(parent)
{
}
virtual ~JsPropertyTree() override
{
}
QMap<QString, JsPropertyItem*> childMap;
};
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);
void clearPropertyTree();
void loadPropertyTree(QJSValue& val, JsPropertyItem* parentItem = nullptr);
QTimer *periodicTimer;
QLineEdit *scriptPath;
QLineEdit *scriptArgs;
QPushButton *browseButton;
QPushButton *stopButton;
QPushButton *startButton;
QPushButton *clearButton;
QTabWidget *tabWidget;
QTextEdit *jsOutput;
JsPropertyTree *propTree;
QtScriptInstance *scriptInstance;
QString emuThreadText;
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__