dolphin/Source/Core/DolphinQt/FIFO/FIFOPlayerWindow.cpp
JosJuice 72cf2bdb87 Audit uses of IsRunning and GetState
Some pieces of code are calling IsRunning because there's some
particular action that only makes sense when emulation is running, for
instance showing the state of the emulated CPU. IsRunning is appropriate
to use for this. Then there are pieces of code that are calling
IsRunning because there's some particular thing they must avoid doing
e.g. when the CPU thread is running or IOS is running. IsRunning isn't
quite appropriate for this. Such code should also be checking for the
states Starting and Stopping. Keep in mind that:

* When the state is Starting, the state can asynchronously change to
  Running at any time.
* When we try to stop the core, the state gets set to Stopping before we
  take any action to actually stop things.

This commit adds a new method Core::IsUninitialized, and changes all
callers of IsRunning and GetState that look to me like they should be
changed.
2024-06-21 20:52:55 +02:00

422 lines
13 KiB
C++

// Copyright 2017 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/FIFO/FIFOPlayerWindow.h"
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QEvent>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QIcon>
#include <QKeyEvent>
#include <QKeySequence>
#include <QLabel>
#include <QPushButton>
#include <QSpinBox>
#include <QTabWidget>
#include <QVBoxLayout>
#include <algorithm>
#include "Core/Core.h"
#include "Core/FifoPlayer/FifoDataFile.h"
#include "Core/FifoPlayer/FifoPlayer.h"
#include "Core/FifoPlayer/FifoRecorder.h"
#include "Core/System.h"
#include "DolphinQt/Config/ToolTipControls/ToolTipCheckBox.h"
#include "DolphinQt/FIFO/FIFOAnalyzer.h"
#include "DolphinQt/QtUtils/DolphinFileDialog.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h"
#include "DolphinQt/QtUtils/QueueOnObject.h"
#include "DolphinQt/Resources.h"
#include "DolphinQt/Settings.h"
FIFOPlayerWindow::FIFOPlayerWindow(FifoPlayer& fifo_player, FifoRecorder& fifo_recorder,
QWidget* parent)
: QWidget(parent), m_fifo_player(fifo_player), m_fifo_recorder(fifo_recorder)
{
setWindowTitle(tr("FIFO Player"));
setWindowIcon(Resources::GetAppIcon());
CreateWidgets();
LoadSettings();
ConnectWidgets();
AddDescriptions();
UpdateInfo();
UpdateControls();
m_fifo_player.SetFileLoadedCallback(
[this] { QueueOnObject(this, &FIFOPlayerWindow::OnFIFOLoaded); });
m_fifo_player.SetFrameWrittenCallback([this] {
QueueOnObject(this, [this] {
UpdateInfo();
UpdateControls();
});
});
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this](Core::State state) {
if (state != m_emu_state)
{
if (state == Core::State::Running && m_emu_state != Core::State::Paused)
OnEmulationStarted();
else if (state == Core::State::Uninitialized)
OnEmulationStopped();
m_emu_state = state;
}
});
installEventFilter(this);
}
FIFOPlayerWindow::~FIFOPlayerWindow()
{
m_fifo_player.SetFileLoadedCallback({});
m_fifo_player.SetFrameWrittenCallback({});
}
void FIFOPlayerWindow::CreateWidgets()
{
auto* layout = new QVBoxLayout;
// Info
auto* info_group = new QGroupBox(tr("File Info"));
auto* info_layout = new QHBoxLayout;
m_info_label = new QLabel;
info_layout->addWidget(m_info_label);
info_group->setLayout(info_layout);
m_info_label->setFixedHeight(QFontMetrics(font()).lineSpacing() * 3);
// Object Range
auto* object_range_group = new QGroupBox(tr("Object Range"));
auto* object_range_layout = new QHBoxLayout;
m_object_range_from = new QSpinBox;
m_object_range_from_label = new QLabel(tr("From:"));
m_object_range_to = new QSpinBox;
m_object_range_to_label = new QLabel(tr("To:"));
object_range_layout->addWidget(m_object_range_from_label);
object_range_layout->addWidget(m_object_range_from);
object_range_layout->addWidget(m_object_range_to_label);
object_range_layout->addWidget(m_object_range_to);
object_range_group->setLayout(object_range_layout);
// Frame Range
auto* frame_range_group = new QGroupBox(tr("Frame Range"));
auto* frame_range_layout = new QHBoxLayout;
m_frame_range_from = new QSpinBox;
m_frame_range_from_label = new QLabel(tr("From:"));
m_frame_range_to = new QSpinBox;
m_frame_range_to_label = new QLabel(tr("To:"));
frame_range_layout->addWidget(m_frame_range_from_label);
frame_range_layout->addWidget(m_frame_range_from);
frame_range_layout->addWidget(m_frame_range_to_label);
frame_range_layout->addWidget(m_frame_range_to);
frame_range_group->setLayout(frame_range_layout);
// Playback Options
auto* playback_group = new QGroupBox(tr("Playback Options"));
auto* playback_layout = new QGridLayout;
m_early_memory_updates = new ToolTipCheckBox(tr("Early Memory Updates"));
m_loop = new ToolTipCheckBox(tr("Loop"));
playback_layout->addWidget(object_range_group, 0, 0);
playback_layout->addWidget(frame_range_group, 0, 1);
playback_layout->addWidget(m_early_memory_updates, 1, 0);
playback_layout->addWidget(m_loop, 1, 1);
playback_group->setLayout(playback_layout);
// Recording Options
auto* recording_group = new QGroupBox(tr("Recording Options"));
auto* recording_layout = new QHBoxLayout;
m_frame_record_count = new QSpinBox;
m_frame_record_count_label = new QLabel(tr("Frames to Record:"));
m_frame_record_count->setMinimum(1);
m_frame_record_count->setMaximum(3600);
m_frame_record_count->setValue(3);
recording_layout->addWidget(m_frame_record_count_label);
recording_layout->addWidget(m_frame_record_count);
recording_group->setLayout(recording_layout);
m_button_box = new QDialogButtonBox(QDialogButtonBox::Close);
// Action Buttons
m_load = m_button_box->addButton(tr("Load..."), QDialogButtonBox::ActionRole);
m_save = m_button_box->addButton(tr("Save..."), QDialogButtonBox::ActionRole);
m_record = m_button_box->addButton(tr("Record"), QDialogButtonBox::ActionRole);
m_stop = m_button_box->addButton(tr("Stop"), QDialogButtonBox::ActionRole);
layout->addWidget(info_group);
layout->addWidget(playback_group);
layout->addWidget(recording_group);
layout->addWidget(m_button_box);
m_main_widget = new QWidget(this);
m_main_widget->setLayout(layout);
m_tab_widget = new QTabWidget(this);
m_analyzer = new FIFOAnalyzer(m_fifo_player);
m_tab_widget->addTab(m_main_widget, tr("Play / Record"));
m_tab_widget->addTab(m_analyzer, tr("Analyze"));
auto* tab_layout = new QVBoxLayout;
tab_layout->addWidget(m_tab_widget);
setLayout(tab_layout);
}
void FIFOPlayerWindow::LoadSettings()
{
m_early_memory_updates->setChecked(Config::Get(Config::MAIN_FIFOPLAYER_EARLY_MEMORY_UPDATES));
m_loop->setChecked(Config::Get(Config::MAIN_FIFOPLAYER_LOOP_REPLAY));
}
void FIFOPlayerWindow::ConnectWidgets()
{
connect(m_load, &QPushButton::clicked, this, &FIFOPlayerWindow::LoadRecording);
connect(m_save, &QPushButton::clicked, this, &FIFOPlayerWindow::SaveRecording);
connect(m_record, &QPushButton::clicked, this, &FIFOPlayerWindow::StartRecording);
connect(m_stop, &QPushButton::clicked, this, &FIFOPlayerWindow::StopRecording);
connect(m_button_box, &QDialogButtonBox::rejected, this, &FIFOPlayerWindow::hide);
connect(m_early_memory_updates, &QCheckBox::toggled, this, &FIFOPlayerWindow::OnConfigChanged);
connect(m_loop, &QCheckBox::toggled, this, &FIFOPlayerWindow::OnConfigChanged);
connect(m_frame_range_from, &QSpinBox::valueChanged, this, &FIFOPlayerWindow::OnLimitsChanged);
connect(m_frame_range_to, &QSpinBox::valueChanged, this, &FIFOPlayerWindow::OnLimitsChanged);
connect(m_object_range_from, &QSpinBox::valueChanged, this, &FIFOPlayerWindow::OnLimitsChanged);
connect(m_object_range_to, &QSpinBox::valueChanged, this, &FIFOPlayerWindow::OnLimitsChanged);
}
void FIFOPlayerWindow::AddDescriptions()
{
static const char TR_MEMORY_UPDATES_DESCRIPTION[] = QT_TR_NOOP(
"If enabled, then all memory updates happen at once before the first frame.<br><br>"
"Causes issues with many fifologs, but can be useful for testing.<br><br>"
"<dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>");
static const char TR_LOOP_DESCRIPTION[] =
QT_TR_NOOP("If unchecked, then playback of the fifolog stops after the final frame.<br><br>"
"This is generally only useful when a frame-dumping option is enabled.<br><br>"
"<dolphin_emphasis>If unsure, leave this checked.</dolphin_emphasis>");
m_early_memory_updates->SetDescription(tr(TR_MEMORY_UPDATES_DESCRIPTION));
m_loop->SetDescription(tr(TR_LOOP_DESCRIPTION));
}
void FIFOPlayerWindow::LoadRecording()
{
QString path = DolphinFileDialog::getOpenFileName(this, tr("Open FIFO log"), QString(),
tr("Dolphin FIFO Log (*.dff)"));
if (path.isEmpty())
return;
emit LoadFIFORequested(path);
}
void FIFOPlayerWindow::SaveRecording()
{
QString path = DolphinFileDialog::getSaveFileName(this, tr("Save FIFO log"), QString(),
tr("Dolphin FIFO Log (*.dff)"));
if (path.isEmpty())
return;
FifoDataFile* file = m_fifo_recorder.GetRecordedFile();
bool result = file->Save(path.toStdString());
if (!result)
{
ModalMessageBox::critical(this, tr("Error"), tr("Failed to save FIFO log."));
}
}
void FIFOPlayerWindow::StartRecording()
{
// Start recording
m_fifo_recorder.StartRecording(m_frame_record_count->value(),
[this] { QueueOnObject(this, [this] { OnRecordingDone(); }); });
UpdateControls();
UpdateInfo();
}
void FIFOPlayerWindow::StopRecording()
{
m_fifo_recorder.StopRecording();
UpdateControls();
UpdateInfo();
}
void FIFOPlayerWindow::OnEmulationStarted()
{
UpdateControls();
if (m_fifo_player.GetFile())
OnFIFOLoaded();
}
void FIFOPlayerWindow::OnEmulationStopped()
{
// If we have previously been recording, stop now.
if (m_fifo_recorder.IsRecording())
StopRecording();
UpdateControls();
// When emulation stops, switch away from the analyzer tab, as it no longer shows anything useful
m_tab_widget->setCurrentWidget(m_main_widget);
m_analyzer->Update();
}
void FIFOPlayerWindow::OnRecordingDone()
{
UpdateInfo();
UpdateControls();
}
void FIFOPlayerWindow::UpdateInfo()
{
if (m_fifo_player.IsPlaying())
{
FifoDataFile* file = m_fifo_player.GetFile();
m_info_label->setText(tr("%1 frame(s)\n%2 object(s)\nCurrent Frame: %3")
.arg(QString::number(file->GetFrameCount()),
QString::number(m_fifo_player.GetCurrentFrameObjectCount()),
QString::number(m_fifo_player.GetCurrentFrameNum())));
return;
}
if (m_fifo_recorder.IsRecordingDone())
{
FifoDataFile* file = m_fifo_recorder.GetRecordedFile();
size_t fifo_bytes = 0;
size_t mem_bytes = 0;
for (u32 i = 0; i < file->GetFrameCount(); ++i)
{
fifo_bytes += file->GetFrame(i).fifoData.size();
for (const auto& mem_update : file->GetFrame(i).memoryUpdates)
mem_bytes += mem_update.data.size();
}
m_info_label->setText(tr("%1 FIFO bytes\n%2 memory bytes\n%3 frames")
.arg(QString::number(fifo_bytes), QString::number(mem_bytes),
QString::number(file->GetFrameCount())));
return;
}
if (Core::IsRunning(Core::System::GetInstance()) && m_fifo_recorder.IsRecording())
{
m_info_label->setText(tr("Recording..."));
return;
}
m_info_label->setText(tr("No file loaded / recorded."));
}
void FIFOPlayerWindow::OnFIFOLoaded()
{
FifoDataFile* file = m_fifo_player.GetFile();
auto object_count = m_fifo_player.GetMaxObjectCount();
auto frame_count = file->GetFrameCount();
m_frame_range_to->setMaximum(frame_count - 1);
m_object_range_to->setMaximum(object_count - 1);
m_frame_range_from->setValue(0);
m_object_range_from->setValue(0);
m_frame_range_to->setValue(frame_count - 1);
m_object_range_to->setValue(object_count - 1);
UpdateInfo();
UpdateLimits();
UpdateControls();
m_analyzer->Update();
}
void FIFOPlayerWindow::OnConfigChanged()
{
Config::SetBase(Config::MAIN_FIFOPLAYER_EARLY_MEMORY_UPDATES,
m_early_memory_updates->isChecked());
Config::SetBase(Config::MAIN_FIFOPLAYER_LOOP_REPLAY, m_loop->isChecked());
}
void FIFOPlayerWindow::OnLimitsChanged()
{
FifoPlayer& player = m_fifo_player;
player.SetFrameRangeStart(m_frame_range_from->value());
player.SetFrameRangeEnd(m_frame_range_to->value());
player.SetObjectRangeStart(m_object_range_from->value());
player.SetObjectRangeEnd(m_object_range_to->value());
UpdateLimits();
}
void FIFOPlayerWindow::UpdateLimits()
{
m_frame_range_from->setMaximum(m_frame_range_to->value());
m_frame_range_to->setMinimum(m_frame_range_from->value());
m_object_range_from->setMaximum(m_object_range_to->value());
m_object_range_to->setMinimum(m_object_range_from->value());
}
void FIFOPlayerWindow::UpdateControls()
{
Core::System& system = Core::System::GetInstance();
const bool core_is_uninitialized = Core::IsUninitialized(system);
const bool core_is_running = Core::IsRunning(system);
const bool is_recording = m_fifo_recorder.IsRecording();
const bool is_playing = m_fifo_player.IsPlaying();
m_frame_range_from->setEnabled(is_playing);
m_frame_range_from_label->setEnabled(is_playing);
m_frame_range_to->setEnabled(is_playing);
m_frame_range_to_label->setEnabled(is_playing);
m_object_range_from->setEnabled(is_playing);
m_object_range_from_label->setEnabled(is_playing);
m_object_range_to->setEnabled(is_playing);
m_object_range_to_label->setEnabled(is_playing);
bool enable_frame_record_count = !is_playing && !is_recording;
m_frame_record_count_label->setEnabled(enable_frame_record_count);
m_frame_record_count->setEnabled(enable_frame_record_count);
m_load->setEnabled(core_is_uninitialized);
m_record->setEnabled(core_is_running && !is_playing);
m_stop->setVisible(core_is_running && is_recording);
m_record->setVisible(!m_stop->isVisible());
m_save->setEnabled(m_fifo_recorder.IsRecordingDone());
}
bool FIFOPlayerWindow::eventFilter(QObject* object, QEvent* event)
{
// Close when escape is pressed
if (event->type() == QEvent::KeyPress)
{
if (static_cast<QKeyEvent*>(event)->matches(QKeySequence::Cancel))
hide();
}
return false;
}