Emu: Implement BlockingCallFromMainThread

Reduces some copy-paste clutter throughout the project
This commit is contained in:
Megamouse 2022-06-24 19:58:26 +02:00 committed by Ivan
parent 7842812e24
commit 9cf7a63c77
13 changed files with 95 additions and 223 deletions

View file

@ -1627,19 +1627,11 @@ void camera_context::operator()()
else else
{ {
std::lock_guard lock(mutex); std::lock_guard lock(mutex);
atomic_t<bool> wake_up = false;
Emu.CallFromMainThread([&]() Emu.BlockingCallFromMainThread([&]()
{ {
send_frame_update_event = handler ? on_handler_state(handler->get_state()) : true; send_frame_update_event = handler ? on_handler_state(handler->get_state()) : true;
wake_up = true;
wake_up.notify_one();
}); });
while (!wake_up && !Emu.IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
} }
} }
@ -1704,9 +1696,8 @@ void camera_context::operator()()
bool camera_context::open_camera() bool camera_context::open_camera()
{ {
bool result = true; bool result = true;
atomic_t<bool> wake_up = false;
Emu.CallFromMainThread([&wake_up, &result, this]() Emu.BlockingCallFromMainThread([&result, this]()
{ {
handler.reset(); handler.reset();
handler = Emu.GetCallbacks().get_camera_handler(); handler = Emu.GetCallbacks().get_camera_handler();
@ -1715,15 +1706,8 @@ bool camera_context::open_camera()
handler->open_camera(); handler->open_camera();
result = on_handler_state(handler->get_state()); result = on_handler_state(handler->get_state());
} }
wake_up = true;
wake_up.notify_one();
}); });
while (!wake_up && !Emu.IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
return result; return result;
} }
@ -1738,20 +1722,11 @@ bool camera_context::start_camera()
handler->set_resolution(info.width, info.height); handler->set_resolution(info.width, info.height);
handler->set_format(info.format, info.bytesize); handler->set_format(info.format, info.bytesize);
atomic_t<bool> wake_up = false; Emu.BlockingCallFromMainThread([&result, this]()
Emu.CallFromMainThread([&wake_up, &result, this]()
{ {
handler->start_camera(); handler->start_camera();
result = on_handler_state(handler->get_state()); result = on_handler_state(handler->get_state());
wake_up = true;
wake_up.notify_one();
}); });
while (!wake_up && !Emu.IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
} }
return result; return result;
@ -1763,19 +1738,10 @@ bool camera_context::get_camera_frame(u8* dst, u32& width, u32& height, u64& fra
if (handler) if (handler)
{ {
atomic_t<bool> wake_up = false; Emu.BlockingCallFromMainThread([&]()
Emu.CallFromMainThread([&]()
{ {
result = on_handler_state(handler->get_image(dst, info.bytesize, width, height, frame_number, bytes_read)); result = on_handler_state(handler->get_image(dst, info.bytesize, width, height, frame_number, bytes_read));
wake_up = true;
wake_up.notify_one();
}); });
while (!wake_up && !Emu.IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
} }
return result; return result;
@ -1785,19 +1751,10 @@ void camera_context::stop_camera()
{ {
if (handler) if (handler)
{ {
atomic_t<bool> wake_up = false; Emu.BlockingCallFromMainThread([this]()
Emu.CallFromMainThread([&wake_up, this]()
{ {
handler->stop_camera(); handler->stop_camera();
wake_up = true;
wake_up.notify_one();
}); });
while (!wake_up && !Emu.IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
} }
} }
@ -1805,19 +1762,10 @@ void camera_context::close_camera()
{ {
if (handler) if (handler)
{ {
atomic_t<bool> wake_up = false; Emu.BlockingCallFromMainThread([this]()
Emu.CallFromMainThread([&wake_up, this]()
{ {
handler->close_camera(); handler->close_camera();
wake_up = true;
wake_up.notify_one();
}); });
while (!wake_up && !Emu.IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
} }
} }

View file

@ -214,8 +214,6 @@ error_code cellOskDialogLoadAsync(u32 container, vm::ptr<CellOskDialogParam> dia
} }
} }
atomic_t<bool> result = false;
osk->on_osk_close = [wptr = std::weak_ptr<OskDialogBase>(osk)](s32 status) osk->on_osk_close = [wptr = std::weak_ptr<OskDialogBase>(osk)](s32 status)
{ {
cellOskDialog.notice("on_osk_close(status=%d)", status); cellOskDialog.notice("on_osk_close(status=%d)", status);
@ -531,26 +529,19 @@ error_code cellOskDialogLoadAsync(u32 container, vm::ptr<CellOskDialogParam> dia
input::SetIntercepted(osk->pad_input_enabled, osk->keyboard_input_enabled, osk->mouse_input_enabled); input::SetIntercepted(osk->pad_input_enabled, osk->keyboard_input_enabled, osk->mouse_input_enabled);
Emu.CallFromMainThread([=, &result, &info]() Emu.BlockingCallFromMainThread([=, &info]()
{ {
osk->Create(get_localized_string(localized_string_id::CELL_OSK_DIALOG_TITLE), message, osk->osk_text, maxLength, prohibitFlgs, allowOskPanelFlg, firstViewPanel, info.base_color.load(), info.dimmer_enabled.load(), false); osk->Create(get_localized_string(localized_string_id::CELL_OSK_DIALOG_TITLE), message, osk->osk_text, maxLength, prohibitFlgs, allowOskPanelFlg, firstViewPanel, info.base_color.load(), info.dimmer_enabled.load(), false);
result = true;
result.notify_one();
if (g_fxo->get<osk_info>().osk_continuous_mode.load() == CELL_OSKDIALOG_CONTINUOUS_MODE_HIDE)
{
sysutil_send_system_cmd(CELL_SYSUTIL_OSKDIALOG_DISPLAY_CHANGED, CELL_OSKDIALOG_DISPLAY_STATUS_SHOW);
}
}); });
if (info.osk_continuous_mode == CELL_OSKDIALOG_CONTINUOUS_MODE_HIDE)
{
sysutil_send_system_cmd(CELL_SYSUTIL_OSKDIALOG_DISPLAY_CHANGED, CELL_OSKDIALOG_DISPLAY_STATUS_SHOW);
}
g_fxo->get<osk_info>().last_dialog_state = CELL_SYSUTIL_OSKDIALOG_LOADED; g_fxo->get<osk_info>().last_dialog_state = CELL_SYSUTIL_OSKDIALOG_LOADED;
sysutil_send_system_cmd(CELL_SYSUTIL_OSKDIALOG_LOADED, 0); sysutil_send_system_cmd(CELL_SYSUTIL_OSKDIALOG_LOADED, 0);
while (!result && !Emu.IsStopped())
{
thread_ctrl::wait_on(result, false);
}
return CELL_OK; return CELL_OK;
} }

View file

@ -983,23 +983,15 @@ error_code sceNpBasicSendMessageGui(vm::cptr<SceNpBasicMessageDetails> msg, sys_
msg_data.data.assign(msg->data.get_ptr(), msg->data.get_ptr() + msg->size); msg_data.data.assign(msg->data.get_ptr(), msg->data.get_ptr() + msg->size);
} }
atomic_t<bool> wake_up = false; bool result = false;
bool result = false;
input::SetIntercepted(true); input::SetIntercepted(true);
Emu.CallFromMainThread([=, &wake_up, &result, msg_data = std::move(msg_data), npids = std::move(npids)]() mutable Emu.BlockingCallFromMainThread([=, &result, msg_data = std::move(msg_data), npids = std::move(npids)]() mutable
{
auto send_dlg = Emu.GetCallbacks().get_sendmessage_dialog();
result = send_dlg->Exec(msg_data, npids);
wake_up = true;
wake_up.notify_one();
});
while (!wake_up && !Emu.IsStopped())
{ {
thread_ctrl::wait_on(wake_up, false); auto send_dlg = Emu.GetCallbacks().get_sendmessage_dialog();
} result = send_dlg->Exec(msg_data, npids);
});
input::SetIntercepted(false); input::SetIntercepted(false);
@ -1154,26 +1146,18 @@ error_code sceNpBasicRecvMessageCustom(u16 mainType, u32 recvOptions, sys_memory
return SCE_NP_BASIC_ERROR_INVALID_ARGUMENT; return SCE_NP_BASIC_ERROR_INVALID_ARGUMENT;
} }
atomic_t<bool> wake_up = false; bool result = false;
bool result = false;
input::SetIntercepted(true); input::SetIntercepted(true);
SceNpBasicMessageRecvAction recv_result; SceNpBasicMessageRecvAction recv_result{};
u64 chosen_msg_id; u64 chosen_msg_id{};
Emu.CallFromMainThread([=, &wake_up, &result, &recv_result, &chosen_msg_id]() Emu.BlockingCallFromMainThread([=, &result, &recv_result, &chosen_msg_id]()
{
auto recv_dlg = Emu.GetCallbacks().get_recvmessage_dialog();
result = recv_dlg->Exec(static_cast<SceNpBasicMessageMainType>(mainType), static_cast<SceNpBasicMessageRecvOptions>(recvOptions), recv_result, chosen_msg_id);
wake_up = true;
wake_up.notify_one();
});
while (!wake_up && !Emu.IsStopped())
{ {
thread_ctrl::wait_on(wake_up, false); auto recv_dlg = Emu.GetCallbacks().get_recvmessage_dialog();
} result = recv_dlg->Exec(static_cast<SceNpBasicMessageMainType>(mainType), static_cast<SceNpBasicMessageRecvOptions>(recvOptions), recv_result, chosen_msg_id);
});
input::SetIntercepted(false); input::SetIntercepted(false);

View file

@ -130,6 +130,37 @@ void fmt_class_string<cfg_mode>::format(std::string& out, u64 arg)
}); });
} }
void Emulator::CallFromMainThread(std::function<void()>&& func, atomic_t<bool>* wake_up, bool track_emu_state, u64 stop_ctr) const
{
if (!track_emu_state)
{
m_cb.call_from_main_thread(std::move(func), wake_up);
return;
}
std::function<void()> final_func = [this, before = IsStopped(), count = (stop_ctr == umax ? +m_stop_ctr : stop_ctr), func = std::move(func)]
{
if (count == m_stop_ctr && before == IsStopped())
{
func();
}
};
m_cb.call_from_main_thread(std::move(final_func), wake_up);
}
void Emulator::BlockingCallFromMainThread(std::function<void()>&& func) const
{
atomic_t<bool> wake_up = false;
CallFromMainThread(std::move(func), &wake_up);
while (!wake_up && !IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
}
void Emulator::Init(bool add_only) void Emulator::Init(bool add_only)
{ {
jit_runtime::initialize(); jit_runtime::initialize();

View file

@ -56,7 +56,7 @@ enum class cfg_mode
struct EmuCallbacks struct EmuCallbacks
{ {
std::function<void(std::function<void()>)> call_from_main_thread; std::function<void(std::function<void()>, atomic_t<bool>*)> call_from_main_thread;
std::function<void(bool)> on_run; // (start_playtime) continuing or going ingame, so start the clock std::function<void(bool)> on_run; // (start_playtime) continuing or going ingame, so start the clock
std::function<void()> on_pause; std::function<void()> on_pause;
std::function<void()> on_resume; std::function<void()> on_resume;
@ -136,23 +136,10 @@ public:
} }
// Call from the GUI thread // Call from the GUI thread
void CallFromMainThread(std::function<void()>&& func, bool track_emu_state = true, u64 stop_ctr = umax) const void CallFromMainThread(std::function<void()>&& func, atomic_t<bool>* wake_up = nullptr, bool track_emu_state = true, u64 stop_ctr = umax) const;
{
if (!track_emu_state)
{
return m_cb.call_from_main_thread(std::move(func));
}
std::function<void()> final_func = [this, before = IsStopped(), count = (stop_ctr == umax ? +m_stop_ctr : stop_ctr), func = std::move(func)] // Blocking call from the GUI thread
{ void BlockingCallFromMainThread(std::function<void()>&& func) const;
if (count == m_stop_ctr && before == IsStopped())
{
func();
}
};
return m_cb.call_from_main_thread(std::move(final_func));
}
enum class stop_counter_t : u64{}; enum class stop_counter_t : u64{};
@ -163,7 +150,7 @@ public:
void CallFromMainThread(std::function<void()>&& func, stop_counter_t counter) const void CallFromMainThread(std::function<void()>&& func, stop_counter_t counter) const
{ {
CallFromMainThread(std::move(func), true, static_cast<u64>(counter)); CallFromMainThread(std::move(func), nullptr, true, static_cast<u64>(counter));
} }
/** Set emulator mode to running unconditionnaly. /** Set emulator mode to running unconditionnaly.

View file

@ -58,9 +58,9 @@ void headless_application::InitializeCallbacks()
return false; return false;
}; };
callbacks.call_from_main_thread = [this](std::function<void()> func) callbacks.call_from_main_thread = [this](std::function<void()> func, atomic_t<bool>* wake_up)
{ {
RequestCallFromMainThread(std::move(func)); RequestCallFromMainThread(std::move(func), wake_up);
}; };
callbacks.init_gs_render = []() callbacks.init_gs_render = []()
@ -156,7 +156,13 @@ void headless_application::InitializeCallbacks()
/** /**
* Using connects avoids timers being unable to be used in a non-qt thread. So, even if this looks stupid to just call func, it's succinct. * Using connects avoids timers being unable to be used in a non-qt thread. So, even if this looks stupid to just call func, it's succinct.
*/ */
void headless_application::CallFromMainThread(const std::function<void()>& func) void headless_application::CallFromMainThread(const std::function<void()>& func, atomic_t<bool>* wake_up)
{ {
func(); func();
if (wake_up)
{
*wake_up = true;
wake_up->notify_one();
}
} }

View file

@ -3,6 +3,7 @@
#include <QCoreApplication> #include <QCoreApplication>
#include "main_application.h" #include "main_application.h"
#include "util/atomic.hpp"
#include <functional> #include <functional>
@ -29,8 +30,8 @@ private:
} }
Q_SIGNALS: Q_SIGNALS:
void RequestCallFromMainThread(std::function<void()> func); void RequestCallFromMainThread(std::function<void()> func, atomic_t<bool>* wake_up);
private Q_SLOTS: private Q_SLOTS:
static void CallFromMainThread(const std::function<void()>& func); static void CallFromMainThread(const std::function<void()>& func, atomic_t<bool>* wake_up);
}; };

View file

@ -338,9 +338,9 @@ void gui_application::InitializeCallbacks()
return false; return false;
}; };
callbacks.call_from_main_thread = [this](std::function<void()> func) callbacks.call_from_main_thread = [this](std::function<void()> func, atomic_t<bool>* wake_up)
{ {
RequestCallFromMainThread(std::move(func)); RequestCallFromMainThread(std::move(func), wake_up);
}; };
callbacks.init_gs_render = []() callbacks.init_gs_render = []()
@ -669,7 +669,13 @@ void gui_application::OnEmuSettingsChange()
/** /**
* Using connects avoids timers being unable to be used in a non-qt thread. So, even if this looks stupid to just call func, it's succinct. * Using connects avoids timers being unable to be used in a non-qt thread. So, even if this looks stupid to just call func, it's succinct.
*/ */
void gui_application::CallFromMainThread(const std::function<void()>& func) void gui_application::CallFromMainThread(const std::function<void()>& func, atomic_t<bool>* wake_up)
{ {
func(); func();
if (wake_up)
{
*wake_up = true;
wake_up->notify_one();
}
} }

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "util/types.hpp" #include "util/types.hpp"
#include "util/atomic.hpp"
#include <QApplication> #include <QApplication>
#include <QElapsedTimer> #include <QElapsedTimer>
@ -95,8 +96,8 @@ Q_SIGNALS:
void OnEnableDiscEject(bool enabled); void OnEnableDiscEject(bool enabled);
void OnEnableDiscInsert(bool enabled); void OnEnableDiscInsert(bool enabled);
void RequestCallFromMainThread(const std::function<void()>& func); void RequestCallFromMainThread(std::function<void()> func, atomic_t<bool>* wake_up);
private Q_SLOTS: private Q_SLOTS:
static void CallFromMainThread(const std::function<void()>& func); static void CallFromMainThread(const std::function<void()>& func, atomic_t<bool>* wake_up);
}; };

View file

@ -1056,7 +1056,7 @@ void main_window::HandlePupInstallation(const QString& file_path, const QString&
Emu.CallFromMainThread([this, str = std::move(str)]() Emu.CallFromMainThread([this, str = std::move(str)]()
{ {
QMessageBox::critical(this, tr("Firmware Installation Failed"), str); QMessageBox::critical(this, tr("Firmware Installation Failed"), str);
}, false); }, nullptr, false);
}; };
if (file_path.isEmpty()) if (file_path.isEmpty())

View file

@ -23,23 +23,13 @@ qt_camera_handler::qt_camera_handler() : camera_handler_base()
qt_camera_handler::~qt_camera_handler() qt_camera_handler::~qt_camera_handler()
{ {
atomic_t<bool> wake_up = false; Emu.BlockingCallFromMainThread([&]()
Emu.CallFromMainThread([&]()
{ {
close_camera(); close_camera();
m_surface.reset(); m_surface.reset();
m_camera.reset(); m_camera.reset();
m_error_handler.reset(); m_error_handler.reset();
wake_up = true;
wake_up.notify_one();
}); });
while (!wake_up)
{
thread_ctrl::wait_on(wake_up, false);
}
} }
void qt_camera_handler::set_camera(const QCameraInfo& camera_info) void qt_camera_handler::set_camera(const QCameraInfo& camera_info)

View file

@ -20,30 +20,20 @@ qt_music_handler::qt_music_handler()
qt_music_handler::~qt_music_handler() qt_music_handler::~qt_music_handler()
{ {
atomic_t<bool> wake_up = false; Emu.BlockingCallFromMainThread([this]()
Emu.CallFromMainThread([&wake_up, this]()
{ {
music_log.notice("Destroying Qt music handler..."); music_log.notice("Destroying Qt music handler...");
m_media_player->stop(); m_media_player->stop();
m_media_player.reset(); m_media_player.reset();
m_error_handler.reset(); m_error_handler.reset();
wake_up = true;
wake_up.notify_one();
}); });
while (!wake_up && !Emu.IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
} }
void qt_music_handler::play(const std::string& path) void qt_music_handler::play(const std::string& path)
{ {
std::lock_guard lock(m_mutex); std::lock_guard lock(m_mutex);
atomic_t<bool> wake_up = false;
Emu.CallFromMainThread([&wake_up, &path, this]() Emu.BlockingCallFromMainThread([&path, this]()
{ {
if (m_state == CELL_MUSIC_PB_STATUS_PAUSE) if (m_state == CELL_MUSIC_PB_STATUS_PAUSE)
{ {
@ -57,141 +47,86 @@ void qt_music_handler::play(const std::string& path)
} }
m_media_player->play(); m_media_player->play();
wake_up = true;
wake_up.notify_one();
}); });
while (!wake_up && !Emu.IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
m_state = CELL_MUSIC_PB_STATUS_PLAY; m_state = CELL_MUSIC_PB_STATUS_PLAY;
} }
void qt_music_handler::stop() void qt_music_handler::stop()
{ {
std::lock_guard lock(m_mutex); std::lock_guard lock(m_mutex);
atomic_t<bool> wake_up = false;
Emu.CallFromMainThread([&wake_up, this]() Emu.BlockingCallFromMainThread([this]()
{ {
music_log.notice("Stopping music..."); music_log.notice("Stopping music...");
m_media_player->stop(); m_media_player->stop();
wake_up = true;
wake_up.notify_one();
}); });
while (!wake_up && !Emu.IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
m_state = CELL_MUSIC_PB_STATUS_STOP; m_state = CELL_MUSIC_PB_STATUS_STOP;
} }
void qt_music_handler::pause() void qt_music_handler::pause()
{ {
std::lock_guard lock(m_mutex); std::lock_guard lock(m_mutex);
atomic_t<bool> wake_up = false;
Emu.CallFromMainThread([&wake_up, this]() Emu.BlockingCallFromMainThread([this]()
{ {
music_log.notice("Pausing music..."); music_log.notice("Pausing music...");
m_media_player->pause(); m_media_player->pause();
wake_up = true;
wake_up.notify_one();
}); });
while (!wake_up && !Emu.IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
m_state = CELL_MUSIC_PB_STATUS_PAUSE; m_state = CELL_MUSIC_PB_STATUS_PAUSE;
} }
void qt_music_handler::fast_forward() void qt_music_handler::fast_forward()
{ {
std::lock_guard lock(m_mutex); std::lock_guard lock(m_mutex);
atomic_t<bool> wake_up = false;
Emu.CallFromMainThread([&wake_up, this]() Emu.BlockingCallFromMainThread([this]()
{ {
music_log.notice("Fast-forwarding music..."); music_log.notice("Fast-forwarding music...");
m_media_player->setPlaybackRate(2.0); m_media_player->setPlaybackRate(2.0);
wake_up = true;
wake_up.notify_one();
}); });
while (!wake_up && !Emu.IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
m_state = CELL_MUSIC_PB_STATUS_FASTFORWARD; m_state = CELL_MUSIC_PB_STATUS_FASTFORWARD;
} }
void qt_music_handler::fast_reverse() void qt_music_handler::fast_reverse()
{ {
std::lock_guard lock(m_mutex); std::lock_guard lock(m_mutex);
atomic_t<bool> wake_up = false;
Emu.CallFromMainThread([&wake_up, this]() Emu.BlockingCallFromMainThread([this]()
{ {
music_log.notice("Fast-reversing music..."); music_log.notice("Fast-reversing music...");
m_media_player->setPlaybackRate(-2.0); m_media_player->setPlaybackRate(-2.0);
wake_up = true;
wake_up.notify_one();
}); });
while (!wake_up && !Emu.IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
m_state = CELL_MUSIC_PB_STATUS_FASTREVERSE; m_state = CELL_MUSIC_PB_STATUS_FASTREVERSE;
} }
void qt_music_handler::set_volume(f32 volume) void qt_music_handler::set_volume(f32 volume)
{ {
std::lock_guard lock(m_mutex); std::lock_guard lock(m_mutex);
atomic_t<bool> wake_up = false;
Emu.CallFromMainThread([&wake_up, &volume, this]() Emu.BlockingCallFromMainThread([&volume, this]()
{ {
const int new_volume = std::max<int>(0, std::min<int>(volume * 100, 100)); const int new_volume = std::max<int>(0, std::min<int>(volume * 100, 100));
music_log.notice("Setting volume to %d%%", new_volume); music_log.notice("Setting volume to %d%%", new_volume);
m_media_player->setVolume(new_volume); m_media_player->setVolume(new_volume);
wake_up = true;
wake_up.notify_one();
}); });
while (!wake_up && !Emu.IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
} }
f32 qt_music_handler::get_volume() const f32 qt_music_handler::get_volume() const
{ {
std::lock_guard lock(m_mutex); std::lock_guard lock(m_mutex);
atomic_t<bool> wake_up = false;
f32 volume = 0.0f; f32 volume = 0.0f;
Emu.CallFromMainThread([&wake_up, &volume, this]() Emu.BlockingCallFromMainThread([&volume, this]()
{ {
const int current_volume = std::max(0, std::min(m_media_player->volume(), 100)); const int current_volume = std::max(0, std::min(m_media_player->volume(), 100));
music_log.notice("Getting volume: %d%%", current_volume); music_log.notice("Getting volume: %d%%", current_volume);
volume = current_volume / 100.0f; volume = current_volume / 100.0f;
wake_up = true;
wake_up.notify_one();
}); });
while (!wake_up && !Emu.IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
return volume; return volume;
} }

View file

@ -40,25 +40,17 @@ s32 save_data_dialog::ShowSaveDataList(std::vector<SaveDataEntry>& save_entries,
} }
// Fall back to front-end GUI // Fall back to front-end GUI
atomic_t<bool> dlg_result(false);
atomic_t<s32> selection = 0; atomic_t<s32> selection = 0;
input::SetIntercepted(true); input::SetIntercepted(true);
Emu.CallFromMainThread([&]() Emu.BlockingCallFromMainThread([&]()
{ {
save_data_list_dialog sdid(save_entries, focused, op, listSet); save_data_list_dialog sdid(save_entries, focused, op, listSet);
sdid.exec(); sdid.exec();
selection = sdid.GetSelection(); selection = sdid.GetSelection();
dlg_result = true;
dlg_result.notify_one();
}); });
while (!dlg_result && !Emu.IsStopped())
{
thread_ctrl::wait_on(dlg_result, false);
}
input::SetIntercepted(false); input::SetIntercepted(false);
if (use_end) sysutil_send_system_cmd(CELL_SYSUTIL_DRAWING_END, 0); if (use_end) sysutil_send_system_cmd(CELL_SYSUTIL_DRAWING_END, 0);