rpcsx/rpcs3/Emu/System.h
Elad 575a245f8d
IDM: Implement lock-free smart pointers (#16403)
Replaces `std::shared_pointer` with `stx::atomic_ptr` and `stx::shared_ptr`.

Notes to programmers:

* This pr kills the use of `dynamic_cast`, `std::dynamic_pointer_cast` and `std::weak_ptr` on IDM objects, possible replacement is to save the object ID on the base object, then use idm::check/get_unlocked to the destination type via the saved ID which may be null. Null pointer check is how you can tell type mismatch (as dynamic cast) or object destruction (as weak_ptr locking).
* Double-inheritance on IDM objects should be used with care, `stx::shared_ptr` does not support constant-evaluated pointer offsetting to parent/child type.
* `idm::check/get_unlocked` can now be used anywhere.

Misc fixes:
* Fixes some segfaults with RPCN with interaction with IDM.
* Fix deadlocks in access violation handler due locking recursion.
* Fixes race condition in process exit-spawn on memory containers read.
* Fix bug that theoretically can prevent RPCS3 from booting - fix `id_manager::typeinfo` comparison to compare members instead of `memcmp` which can fail spuriously on padding bytes.
* Ensure all IDM inherited types of base, either has `id_base` or `id_type` defined locally, this allows to make getters such as `idm::get_unlocked<lv2_socket, lv2_socket_raw>()` which were broken before. (requires save-states invalidation)
* Removes broken operator[] overload of `stx::shared_ptr` and `stx::single_ptr` for non-array types.
2024-12-22 20:59:48 +02:00

464 lines
13 KiB
C++

#pragma once
#include "util/types.hpp"
#include "util/atomic.hpp"
#include "util/shared_ptr.hpp"
#include "Utilities/bit_set.h"
#include "config_mode.h"
#include "games_config.h"
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include <set>
#include "Emu/Cell/timers.hpp"
void init_fxo_for_exec(utils::serial*, bool);
enum class localized_string_id;
enum class video_renderer;
class spu_thread;
template <typename T>
class named_thread;
namespace cfg
{
class _base;
}
enum class system_state : u32
{
stopped,
loading,
stopping,
running,
paused,
frozen, // paused but cannot resume
ready,
starting,
};
enum class game_boot_result : u32
{
no_errors,
generic_error,
nothing_to_boot,
wrong_disc_location,
invalid_file_or_folder,
invalid_bdvd_folder,
install_failed,
decryption_error,
file_creation_error,
firmware_missing,
unsupported_disc_type,
savestate_corrupted,
savestate_version_unsupported,
still_running,
already_added,
currently_restricted,
};
constexpr bool is_error(game_boot_result res)
{
return res != game_boot_result::no_errors;
}
struct EmuCallbacks
{
std::function<void(std::function<void()>, atomic_t<u32>*)> call_from_main_thread;
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_resume;
std::function<void()> on_stop;
std::function<void()> on_ready;
std::function<void()> on_missing_fw;
std::function<void(std::shared_ptr<atomic_t<bool>>, int)> on_emulation_stop_no_response;
std::function<void(std::shared_ptr<atomic_t<bool>>, stx::shared_ptr<utils::serial>, stx::atomic_ptr<std::string>*, std::shared_ptr<void>)> on_save_state_progress;
std::function<void(bool enabled)> enable_disc_eject;
std::function<void(bool enabled)> enable_disc_insert;
std::function<bool(bool, std::function<void()>)> try_to_quit; // (force_quit, on_exit) Try to close RPCS3
std::function<void(s32, s32)> handle_taskbar_progress; // (type, value) type: 0 for reset, 1 for increment, 2 for set_limit, 3 for set_value
std::function<void()> init_kb_handler;
std::function<void()> init_mouse_handler;
std::function<void(std::string_view title_id)> init_pad_handler;
std::function<void()> update_emu_settings;
std::function<void()> save_emu_settings;
std::function<std::unique_ptr<class GSFrameBase>()> get_gs_frame;
std::function<std::shared_ptr<class camera_handler_base>()> get_camera_handler;
std::function<std::shared_ptr<class music_handler_base>()> get_music_handler;
std::function<void(utils::serial*)> init_gs_render;
std::function<std::shared_ptr<class AudioBackend>()> get_audio;
std::function<std::shared_ptr<class audio_device_enumerator>(u64)> get_audio_enumerator; // (audio_renderer)
std::function<std::shared_ptr<class MsgDialogBase>()> get_msg_dialog;
std::function<std::shared_ptr<class OskDialogBase>()> get_osk_dialog;
std::function<std::unique_ptr<class SaveDialogBase>()> get_save_dialog;
std::function<std::shared_ptr<class SendMessageDialogBase>()> get_sendmessage_dialog;
std::function<std::shared_ptr<class RecvMessageDialogBase>()> get_recvmessage_dialog;
std::function<std::unique_ptr<class TrophyNotificationBase>()> get_trophy_notification_dialog;
std::function<std::string(localized_string_id, const char*)> get_localized_string;
std::function<std::u32string(localized_string_id, const char*)> get_localized_u32string;
std::function<std::string(const cfg::_base*, u32)> get_localized_setting;
std::function<void(const std::string&)> play_sound;
std::function<bool(const std::string&, std::string&, s32&, s32&, s32&)> get_image_info; // (filename, sub_type, width, height, CellSearchOrientation)
std::function<bool(const std::string&, s32, s32, s32&, s32&, u8*, bool)> get_scaled_image; // (filename, target_width, target_height, width, height, dst, force_fit)
std::string(*resolve_path)(std::string_view) = [](std::string_view arg){ return std::string{arg}; }; // Resolve path using Qt
std::function<std::vector<std::string>()> get_font_dirs;
std::function<bool(const std::vector<std::string>&)> on_install_pkgs;
std::function<void(u32)> add_breakpoint;
};
namespace utils
{
struct serial;
};
class Emulator final
{
atomic_t<system_state> m_state{system_state::stopped};
EmuCallbacks m_cb;
atomic_t<u64> m_pause_start_time{0}; // set when paused
atomic_t<u64> m_pause_amend_time{0}; // increased when resumed
atomic_t<u64> m_stop_ctr{1}; // Increments when emulation is stopped
atomic_t<bool> m_emu_state_close_pending = false;
atomic_t<u64> m_restrict_emu_state_change{0};
games_config m_games_config;
video_renderer m_default_renderer;
std::string m_default_graphics_adapter;
cfg_mode m_config_mode = cfg_mode::custom;
std::string m_config_path;
std::string m_path;
std::string m_path_old;
std::string m_path_original;
std::string m_title_id;
std::string m_title;
std::string m_app_version;
std::string m_hash;
std::string m_cat;
std::string m_dir;
std::string m_sfo_dir;
std::string m_game_dir{"PS3_GAME"};
std::string m_usr{"00000001"};
u32 m_usrid{1};
std::shared_ptr<utils::serial> m_ar;
// This flag should be adjusted before each Kill() or each BootGame() and similar because:
// 1. It forces an application to boot immediately by calling Run() in Load().
// 2. It signifies that we don't want to exit on Kill(), for example if we want to transition to another application.
bool m_force_boot = false;
bool m_has_gui = true;
bool m_state_inspection_savestate = false;
usz m_tty_file_init_pos = umax;
std::vector<std::shared_ptr<atomic_t<u32>>> m_pause_msgs_refs;
std::vector<std::function<void()>> m_postponed_init_code;
void ExecPostponedInitCode()
{
for (auto&& func : ::as_rvalue(std::move(m_postponed_init_code)))
{
func();
}
}
enum class SaveStateExtentionFlags1 : u8
{
SupportsMenuOpenResume,
ShouldCloseMenu,
__bitset_enum_max,
};
bs_t<SaveStateExtentionFlags1> m_savestate_extension_flags1{};
public:
static constexpr std::string_view game_id_boot_prefix = "%RPCS3_GAMEID%:";
static constexpr std::string_view vfs_boot_prefix = "%RPCS3_VFS%:";
Emulator() noexcept = default;
~Emulator() noexcept = default;
void SetCallbacks(EmuCallbacks&& cb)
{
m_cb = std::move(cb);
}
const auto& GetCallbacks() const
{
return m_cb;
}
// Call from the GUI thread
void CallFromMainThread(std::function<void()>&& func, atomic_t<u32>* wake_up = nullptr, bool track_emu_state = true, u64 stop_ctr = umax,
std::source_location src_loc = std::source_location::current()) const;
// Blocking call from the GUI thread
void BlockingCallFromMainThread(std::function<void()>&& func, std::source_location src_loc = std::source_location::current()) const;
enum class stop_counter_t : u64{};
// Returns a different value each time we start a new emulation.
stop_counter_t GetEmulationIdentifier(bool subtract_one = false) const
{
if (subtract_one)
{
return stop_counter_t{m_stop_ctr - 1};
}
return stop_counter_t{+m_stop_ctr};
}
void CallFromMainThread(std::function<void()>&& func, stop_counter_t counter,
std::source_location src_loc = std::source_location::current()) const
{
CallFromMainThread(std::move(func), nullptr, true, static_cast<u64>(counter), src_loc);
}
void PostponeInitCode(std::function<void()>&& func)
{
m_postponed_init_code.emplace_back(std::move(func));
}
/** Set emulator mode to running unconditionnaly.
* Required to execute various part (PPUInterpreter, memory manager...) outside of rpcs3.
*/
void SetTestMode()
{
m_state = system_state::running;
}
void Init();
std::vector<std::string> argv;
std::vector<std::string> envp;
std::vector<u8> data;
std::vector<u128> klic;
std::string disc;
std::string hdd1;
std::function<void(u32)> init_mem_containers;
std::function<void()> after_kill_callback;
u32 m_boot_source_type = 0; // CELL_GAME_GAMETYPE_SYS
const u32& GetBootSourceType() const
{
return m_boot_source_type;
}
const std::string& GetBoot() const
{
return m_path;
}
const std::string& GetLastBoot() const
{
return m_path_original;
}
const std::string& GetTitleID() const
{
return m_title_id;
}
const std::string& GetTitle() const
{
return m_title;
}
const std::string GetTitleAndTitleID() const
{
return m_title + (m_title_id.empty() ? "" : " [" + m_title_id + "]");
}
const std::string& GetAppVersion() const
{
return m_app_version;
}
const std::string& GetExecutableHash() const
{
return m_hash;
}
void SetExecutableHash(std::string hash) { m_hash = std::move(hash); }
const std::string& GetCat() const
{
return m_cat;
}
const std::string& GetFakeCat() const;
const std::string& GetDir() const
{
return m_dir;
}
const std::string GetSfoDir(bool prefer_disc_sfo) const;
// String for GUI dialogs.
const std::string& GetUsr() const
{
return m_usr;
}
const games_config& GetGamesConfig() const
{
return m_games_config;
}
// Get deserialization manager
utils::serial* DeserialManager() const;
// u32 for cell.
u32 GetUsrId() const
{
return m_usrid;
}
void SetUsr(const std::string& user);
std::string GetBackgroundPicturePath() const;
u64 GetPauseTime() const
{
return m_pause_amend_time;
}
const std::string& GetUsedConfig() const
{
return m_config_path;
}
bool IsChildProcess() const
{
return m_config_mode == cfg_mode::continuous;
}
class emulation_state_guard_t
{
class Emulator* _this = nullptr;
bool active = true;
public:
explicit emulation_state_guard_t(Emulator* this0) noexcept
: _this(this0)
{
_this->m_restrict_emu_state_change++;
}
~emulation_state_guard_t() noexcept
{
if (active)
{
_this->m_restrict_emu_state_change--;
}
}
emulation_state_guard_t(emulation_state_guard_t&& rhs) noexcept
{
_this = rhs._this;
active = std::exchange(rhs.active, false);
}
emulation_state_guard_t& operator=(const emulation_state_guard_t&) = delete;
emulation_state_guard_t(const emulation_state_guard_t&) = delete;
};
emulation_state_guard_t MakeEmulationStateGuard()
{
return emulation_state_guard_t{this};
}
game_boot_result BootGame(const std::string& path, const std::string& title_id = "", bool direct = false, cfg_mode config_mode = cfg_mode::custom, const std::string& config_path = "");
bool BootRsxCapture(const std::string& path);
void SetForceBoot(bool force_boot);
game_boot_result Load(const std::string& title_id = "", bool is_disc_patch = false, usz recursion_count = 0);
void Run(bool start_playtime);
void RunPPU();
void FixGuestTime();
void FinalizeRunRequest();
bool IsBootingRestricted() const
{
return m_restrict_emu_state_change != 0;
}
private:
struct savestate_stage
{
bool prepared = false;
std::vector<std::pair<shared_ptr<named_thread<spu_thread>>, u32>> paused_spus;
};
public:
bool Pause(bool freeze_emulation = false, bool show_resume_message = true);
void Resume();
void GracefulShutdown(bool allow_autoexit = true, bool async_op = false, bool savestate = false);
void Kill(bool allow_autoexit = true, bool savestate = false, savestate_stage* stage = nullptr);
game_boot_result Restart(bool graceful = true);
bool Quit(bool force_quit);
static void CleanUp();
bool IsRunning() const { return m_state == system_state::running; }
bool IsPaused() const { return m_state >= system_state::paused; } // ready/starting are also considered paused by this function
bool IsStopped(bool test_fully = false) const { return test_fully ? m_state == system_state::stopped : m_state <= system_state::stopping; }
bool IsReady() const { return m_state == system_state::ready; }
bool IsStarting() const { return m_state == system_state::starting; }
auto GetStatus(bool fixup = true) const { system_state state = m_state; return fixup && state == system_state::frozen ? system_state::paused : fixup && state == system_state::stopping ? system_state::stopped : state; }
bool HasGui() const { return m_has_gui; }
void SetHasGui(bool has_gui) { m_has_gui = has_gui; }
void SetDefaultRenderer(video_renderer renderer) { m_default_renderer = renderer; }
void SetDefaultGraphicsAdapter(std::string adapter) { m_default_graphics_adapter = std::move(adapter); }
std::string GetFormattedTitle(double fps) const;
void ConfigurePPUCache() const;
std::set<std::string> GetGameDirs() const;
u32 AddGamesFromDir(const std::string& path);
game_boot_result AddGame(const std::string& path);
game_boot_result AddGameToYml(const std::string& path);
u32 RemoveGames(const std::vector<std::string>& title_id_list, bool save_on_disk = true);
game_boot_result RemoveGameFromYml(const std::string& title_id);
// Check if path is inside the specified directory
bool IsPathInsideDir(std::string_view path, std::string_view dir) const;
game_boot_result VerifyPathCasing(std::string_view path, std::string_view dir, bool from_dir) const;
void EjectDisc();
game_boot_result InsertDisc(const std::string& path);
static game_boot_result GetElfPathFromDir(std::string& elf_path, const std::string& path);
static void GetBdvdDir(std::string& bdvd_dir, std::string& sfb_dir, std::string& game_dir, const std::string& elf_dir);
friend void init_fxo_for_exec(utils::serial*, bool);
static bool IsVsh();
static bool IsValidSfb(const std::string& path);
static void SaveSettings(const std::string& settings, const std::string& title_id);
};
extern Emulator Emu;
extern bool g_log_all_errors;
extern bool g_use_rtm;
extern u64 g_rtm_tx_limit1;
extern u64 g_rtm_tx_limit2;