Use message queues

This commit is contained in:
offtkp 2024-01-27 18:20:42 +02:00
parent 63f54478f0
commit a473a34794
7 changed files with 266 additions and 269 deletions

View file

@ -14,7 +14,7 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION
endif()
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE RelWithDebInfo)
set(CMAKE_BUILD_TYPE Release)
endif()
project(Alber)

View file

@ -9,6 +9,8 @@
// Forward-declare this since it's just passed and we don't want to include memory.hpp and increase compile time
class Memory;
constexpr u32 badCheatHandle = 0xFFFFFFFF;
class Cheats {
public:
enum class CheatType {
@ -24,6 +26,7 @@ class Cheats {
Cheats(Memory& mem, HIDService& hid);
u32 addCheat(const Cheat& cheat);
u32 addCheat(const u8* data, size_t size);
void removeCheat(u32 id);
void enableCheat(u32 id);
void disableCheat(u32 id);

View file

@ -1,15 +1,15 @@
#pragma once
#include <filesystem>
#include <memory>
#include <QAction>
#include <QWidget>
#include <filesystem>
#include <memory>
#include "emulator.hpp"
class QListWidget;
class CheatsWindow final : public QWidget
{
class CheatsWindow final : public QWidget {
Q_OBJECT
public:

View file

@ -17,12 +17,19 @@
#include "panda_qt/text_editor.hpp"
#include "services/hid.hpp"
struct CheatMessage
{
u32 handle;
std::vector<uint8_t> cheat;
std::function<void(u32)> callback;
};
class MainWindow : public QMainWindow {
Q_OBJECT
private:
// Types of messages we might send from the GUI thread to the emulator thread
enum class MessageType { LoadROM, Reset, Pause, Resume, TogglePause, DumpRomFS, PressKey, ReleaseKey, LoadLuaScript };
enum class MessageType { LoadROM, Reset, Pause, Resume, TogglePause, DumpRomFS, PressKey, ReleaseKey, LoadLuaScript, EditCheat };
// Tagged union representing our message queue messages
struct EmulatorMessage {
@ -40,6 +47,10 @@ class MainWindow : public QMainWindow {
struct {
std::string* str;
} string;
struct {
CheatMessage* c;
} cheat;
};
};
@ -59,8 +70,6 @@ class MainWindow : public QMainWindow {
TextEditorWindow* luaEditor;
QMenuBar* menuBar = nullptr;
QAction* cheatsEditorAction = nullptr;
void swapEmuBuffer();
void emuThreadMainLoop();
void selectLuaFile();
@ -83,4 +92,5 @@ class MainWindow : public QMainWindow {
void keyPressEvent(QKeyEvent* event) override;
void keyReleaseEvent(QKeyEvent* event) override;
void loadLuaScript(const std::string& code);
void editCheat(u32 handle, const std::vector<uint8_t>& cheat, const std::function<void(u32)>& callback);
};

View file

@ -1,4 +1,5 @@
#include "cheats.hpp"
#include "swap.hpp"
Cheats::Cheats(Memory& mem, HIDService& hid) : ar(mem, hid) { reset(); }
@ -23,6 +24,27 @@ u32 Cheats::addCheat(const Cheat& cheat) {
return cheats.size() - 1;
}
u32 Cheats::addCheat(const u8* data, size_t size) {
if ((size % 8) != 0) {
return badCheatHandle;
}
Cheats::Cheat cheat;
cheat.enabled = true;
cheat.type = Cheats::CheatType::ActionReplay;
for (size_t i = 0; i < size; i += 8) {
auto read32 = [](const u8* ptr) { return (u32(ptr[3]) << 24) | (u32(ptr[2]) << 16) | (u32(ptr[1]) << 8) | u32(ptr[0]); };
// Data is passed to us in big endian so we bswap
u32 firstWord = Common::swap32(read32(data + i));
u32 secondWord = Common::swap32(read32(data + i + 4));
cheat.instructions.insert(cheat.instructions.end(), {firstWord, secondWord});
}
return addCheat(cheat);
}
void Cheats::removeCheat(u32 id) {
if (id >= cheats.size()) {
return;

View file

@ -1,79 +1,47 @@
#include "panda_qt/cheats_window.hpp"
#include <qlistwidget.h>
#include <QVBoxLayout>
#include <QListWidget>
#include <QPushButton>
#include <QLabel>
#include <QCheckBox>
#include <QDialog>
#include <QLineEdit>
#include <QTextEdit>
#include <QDialogButtonBox>
#include <QLabel>
#include <QLineEdit>
#include <QListWidget>
#include <QPushButton>
#include <QTextEdit>
#include <QVBoxLayout>
#include "cheats.hpp"
#include "emulator.hpp"
#include "panda_qt/main_window.hpp"
using CheatHandle = u32;
MainWindow* mainWindow = nullptr;
CheatHandle BAD_CHEAT = 0xFFFFFFFF;
struct CheatMetadata
{
CheatHandle handle = BAD_CHEAT;
struct CheatMetadata {
u32 handle = badCheatHandle;
std::string name = "New cheat";
std::string code;
bool enabled = true;
};
u32 addCheat(Emulator* emu, u8* data, size_t size)
{
if ((size % 8) != 0) {
return BAD_CHEAT;
}
Cheats::Cheat cheat;
cheat.enabled = true;
cheat.type = Cheats::CheatType::ActionReplay;
for (size_t i = 0; i < size; i += 8) {
auto read32 = [](const u8* ptr) { return (u32(ptr[3]) << 24) | (u32(ptr[2]) << 16) | (u32(ptr[1]) << 8) | u32(ptr[0]); };
// Data is passed to us in big endian so we bswap
u32 firstWord = Common::swap32(read32(data + i));
u32 secondWord = Common::swap32(read32(data + i + 4));
cheat.instructions.insert(cheat.instructions.end(), {firstWord, secondWord});
}
return emu->getCheats().addCheat(cheat);
}
class CheatEntryWidget : public QWidget
{
class CheatEntryWidget : public QWidget {
public:
CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListWidget* parent);
void Update()
{
void Update() {
name->setText(metadata.name.c_str());
enabled->setChecked(metadata.enabled);
update();
}
void Remove()
{
void Remove() {
emu->getCheats().removeCheat(metadata.handle);
cheatList->takeItem(cheatList->row(listItem));
deleteLater();
}
const CheatMetadata& GetMetadata()
{
return metadata;
}
const CheatMetadata& GetMetadata() { return metadata; }
void SetMetadata(const CheatMetadata& metadata)
{
this->metadata = metadata;
}
void SetMetadata(const CheatMetadata& metadata) { this->metadata = metadata; }
private:
void checkboxChanged(int state);
@ -88,13 +56,13 @@ private:
QListWidgetItem* listItem;
};
class CheatEditDialog : public QDialog
{
class CheatEditDialog : public QDialog {
public:
CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry);
void accepted();
void rejected();
private:
Emulator* emu;
CheatEntryWidget& cheatEntry;
@ -103,8 +71,7 @@ private:
};
CheatEntryWidget::CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListWidget* parent)
: QWidget(), emu(emu), metadata(metadata), cheatList(parent)
{
: QWidget(), emu(emu), metadata(metadata), cheatList(parent) {
QHBoxLayout* layout = new QHBoxLayout;
enabled = new QCheckBox;
@ -127,35 +94,28 @@ CheatEntryWidget::CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListW
parent->setItemWidget(listItem, this);
}
void CheatEntryWidget::checkboxChanged(int state)
{
void CheatEntryWidget::checkboxChanged(int state) {
bool enabled = state == Qt::Checked;
if (metadata.handle == BAD_CHEAT)
{
if (metadata.handle == badCheatHandle) {
printf("Cheat handle is bad, this shouldn't happen\n");
return;
}
if (enabled)
{
if (enabled) {
emu->getCheats().enableCheat(metadata.handle);
metadata.enabled = true;
}
else
{
} else {
emu->getCheats().disableCheat(metadata.handle);
metadata.enabled = false;
}
}
void CheatEntryWidget::editClicked()
{
void CheatEntryWidget::editClicked() {
CheatEditDialog* dialog = new CheatEditDialog(emu, *this);
dialog->show();
}
CheatEditDialog::CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry) : QDialog(), emu(emu), cheatEntry(cheatEntry)
{
CheatEditDialog::CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry) : QDialog(), emu(emu), cheatEntry(cheatEntry) {
setAttribute(Qt::WA_DeleteOnClose);
setModal(true);
@ -173,21 +133,16 @@ CheatEditDialog::CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry) :
font.setPointSize(10);
codeEdit->setFont(font);
if (metadata.code.size() != 0)
{
if (metadata.code.size() != 0) {
// Nicely format it like so:
// 01234567 89ABCDEF
// 01234567 89ABCDEF
std::string formattedCode;
for (size_t i = 0; i < metadata.code.size(); i += 2)
{
for (size_t i = 0; i < metadata.code.size(); i += 2) {
if (i != 0) {
if (i % 8 == 0 && i % 16 != 0)
{
if (i % 8 == 0 && i % 16 != 0) {
formattedCode += " ";
}
else if (i % 16 == 0)
{
} else if (i % 16 == 0) {
formattedCode += "\n";
}
}
@ -211,58 +166,41 @@ CheatEditDialog::CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry) :
connect(this, &QDialog::accepted, this, &CheatEditDialog::accepted);
}
void CheatEditDialog::accepted()
{
void CheatEditDialog::accepted() {
QString code = codeEdit->toPlainText();
code.replace(QRegularExpression("[^0-9a-fA-F]"), "");
CheatMetadata metadata = cheatEntry.GetMetadata();
bool isEditing = metadata.handle != BAD_CHEAT;
metadata.name = nameEdit->text().toStdString();
metadata.code = code.toStdString();
std::vector<uint8_t> bytes;
for (size_t i = 0; i < metadata.code.size(); i += 2)
{
for (size_t i = 0; i < metadata.code.size(); i += 2) {
std::string hex = metadata.code.substr(i, 2);
bytes.push_back((uint8_t)std::stoul(hex, nullptr, 16));
}
if (isEditing)
{
emu->getCheats().removeCheat(metadata.handle);
u32 handle = addCheat(emu, bytes.data(), bytes.size());
mainWindow->editCheat(cheatEntry.GetMetadata().handle, bytes, [this](u32 handle) {
CheatMetadata metadata = cheatEntry.GetMetadata();
metadata.handle = handle;
cheatEntry.SetMetadata(metadata);
}
else
{
if (metadata.name.empty())
{
metadata.name = tr("Cheat code").toStdString();
}
u32 handle = addCheat(emu, bytes.data(), bytes.size());
metadata.handle = handle;
cheatEntry.SetMetadata(metadata);
}
cheatEntry.Update();
});
}
void CheatEditDialog::rejected()
{
bool isEditing = cheatEntry.GetMetadata().handle != BAD_CHEAT;
void CheatEditDialog::rejected() {
bool isEditing = cheatEntry.GetMetadata().handle != badCheatHandle;
if (!isEditing)
{
if (!isEditing) {
// Was adding a cheat but pressed cancel
cheatEntry.Remove();
}
}
CheatsWindow::CheatsWindow(Emulator* emu, const std::filesystem::path& cheatPath, QWidget* parent)
: QWidget(parent, Qt::Window), emu(emu), cheatPath(cheatPath)
{
: QWidget(parent, Qt::Window), emu(emu), cheatPath(cheatPath) {
mainWindow = static_cast<MainWindow*>(parent);
QVBoxLayout* layout = new QVBoxLayout;
layout->setContentsMargins(6, 6, 6, 6);
setLayout(layout);
@ -292,19 +230,16 @@ CheatsWindow::CheatsWindow(Emulator* emu, const std::filesystem::path& cheatPath
// }
}
void CheatsWindow::addEntry()
{
void CheatsWindow::addEntry() {
// CheatEntryWidget is added to the list when it's created
CheatEntryWidget* entry = new CheatEntryWidget(emu, {BAD_CHEAT, "New cheat", "", true}, cheatList);
CheatEntryWidget* entry = new CheatEntryWidget(emu, {badCheatHandle, "New cheat", "", true}, cheatList);
CheatEditDialog* dialog = new CheatEditDialog(emu, *entry);
dialog->show();
}
void CheatsWindow::removeClicked()
{
void CheatsWindow::removeClicked() {
QListWidgetItem* item = cheatList->currentItem();
if (item == nullptr)
{
if (item == nullptr) {
return;
}

View file

@ -6,6 +6,8 @@
#include <cstdio>
#include <fstream>
#include "cheats.hpp"
MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent), screen(this) {
setWindowTitle("Alber");
// Enable drop events for loading ROMs
@ -48,8 +50,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
auto dumpRomFSAction = toolsMenu->addAction(tr("Dump RomFS"));
auto luaEditorAction = toolsMenu->addAction(tr("Open Lua Editor"));
cheatsEditorAction = toolsMenu->addAction(tr("Open Cheats Editor"));
cheatsEditorAction->setEnabled(false);
auto cheatsEditorAction = toolsMenu->addAction(tr("Open Cheats Editor"));
connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS);
connect(luaEditorAction, &QAction::triggered, this, &MainWindow::openLuaEditor);
connect(cheatsEditorAction, &QAction::triggered, this, &MainWindow::openCheatsEditor);
@ -63,7 +64,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
// Set up misc objects
aboutWindow = new AboutWindow(nullptr);
configWindow = new ConfigWindow(this);
cheatsEditor = new CheatsWindow(emu, {});
cheatsEditor = new CheatsWindow(emu, {}, this);
luaEditor = new TextEditorWindow(this, "script.lua", "");
auto args = QCoreApplication::arguments();
@ -245,7 +246,6 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) {
switch (message.type) {
case MessageType::LoadROM:
emu->loadROM(*message.path.p);
cheatsEditorAction->setEnabled(true);
// Clean up the allocated path
delete message.path.p;
break;
@ -255,6 +255,21 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) {
delete message.string.str;
break;
case MessageType::EditCheat: {
u32 handle = message.cheat.c->handle;
const std::vector<uint8_t>& cheat = message.cheat.c->cheat;
const std::function<void(u32)>& callback = message.cheat.c->callback;
bool isEditing = handle != badCheatHandle;
if (isEditing) {
emu->getCheats().removeCheat(handle);
u32 handle = emu->getCheats().addCheat(cheat.data(), cheat.size());
} else {
u32 handle = emu->getCheats().addCheat(cheat.data(), cheat.size());
callback(handle);
}
delete message.cheat.c;
} break;
case MessageType::Pause: emu->pause(); break;
case MessageType::Resume: emu->resume(); break;
case MessageType::TogglePause: emu->togglePause(); break;
@ -327,3 +342,15 @@ void MainWindow::loadLuaScript(const std::string& code) {
message.string.str = new std::string(code);
sendMessage(message);
}
void MainWindow::editCheat(u32 handle, const std::vector<uint8_t>& cheat, const std::function<void(u32)>& callback) {
EmulatorMessage message{.type = MessageType::EditCheat};
CheatMessage* c = new CheatMessage();
c->handle = handle;
c->cheat = cheat;
c->callback = callback;
message.cheat.c = c;
sendMessage(message);
}