namespace DVDThread { struct ReadRequest { bool copy_to_ram; u32 output_address; u64 dvd_offset; u32 length; DiscIO::Partition partition; // This determines which code DVDInterface will run to reply // to the emulated software. We can't use callbacks, // because function pointers can't be stored in savestates. DVDInterface::ReplyType reply_type; // IDs are used to uniquely identify a request. They must not be // identical to IDs of any other requests that currently exist, but // it's fine to re-use IDs of requests that have existed in the past. u64 id; // Only used for logging u64 time_started_ticks; u64 realtime_started_us; u64 realtime_done_us; }; using ReadResult = std::pair<ReadRequest, std::vector<u8>>; static void StartDVDThread(); static void StopDVDThread(); static void DVDThread(); static void WaitUntilIdle(); static void StartReadInternal(bool copy_to_ram, u32 output_address, u64 dvd_offset, u32 length, const DiscIO::Partition& partition, DVDInterface::ReplyType reply_type, s64 ticks_until_completion); static void FinishRead(u64 id, s64 cycles_late); static CoreTiming::EventType* s_finish_read; static u64 s_next_id = 0; static std::thread s_dvd_thread; static Common::Event s_request_queue_expanded; // Is set by CPU thread static Common::Event s_result_queue_expanded; // Is set by DVD thread static Common::Flag s_dvd_thread_exiting(false); // Is set by CPU thread static Common::SPSCQueue<ReadRequest, false> s_request_queue; static Common::SPSCQueue<ReadResult, false> s_result_queue; static std::map<u64, ReadResult> s_result_map; static std::unique_ptr<DiscIO::Volume> s_disc; void Start() { s_finish_read = CoreTiming::RegisterEvent("FinishReadDVDThread", FinishRead); s_request_queue_expanded.Reset(); s_result_queue_expanded.Reset(); s_request_queue.Clear(); s_result_queue.Clear(); // This is reset on every launch for determinism, but it doesn't matter // much, because this will never get exposed to the emulated game. s_next_id = 0; StartDVDThread(); } static void StartDVDThread() { ASSERT(!s_dvd_thread.joinable()); s_dvd_thread_exiting.Clear(); s_dvd_thread = std::thread(DVDThread); } void Stop() { StopDVDThread(); s_disc.reset(); } static void StopDVDThread() { ASSERT(s_dvd_thread.joinable()); // By setting s_DVD_thread_exiting, we ask the DVD thread to cleanly exit. // In case the request queue is empty, we need to set s_request_queue_expanded // so that the DVD thread will wake up and check s_DVD_thread_exiting. s_dvd_thread_exiting.Set(); s_request_queue_expanded.Set(); s_dvd_thread.join(); } void DoState(PointerWrap& p) { // By waiting for the DVD thread to be done working, we ensure // that s_request_queue will be empty and that the DVD thread // won't be touching anything while this function runs. WaitUntilIdle(); // Move all results from s_result_queue to s_result_map because // PointerWrap::Do supports std::map but not Common::SPSCQueue. // This won't affect the behavior of FinishRead. ReadResult result; while (s_result_queue.Pop(result)) s_result_map.emplace(result.first.id, std::move(result)); // Both queues are now empty, so we don't need to savestate them. p.Do(s_result_map); p.Do(s_next_id); // s_disc isn't savestated (because it points to files on the // local system). Instead, we check that the status of the disc // is the same as when the savestate was made. This won't catch // cases of having the wrong disc inserted, though. // TODO: Check the game ID, disc number, revision? bool had_disc = HasDisc(); p.Do(had_disc); if (had_disc != HasDisc()) { if (had_disc) PanicAlertT("An inserted disc was expected but not found."); else s_disc.reset(); } // TODO: Savestates can be smaller if the buffers of results aren't saved, // but instead get re-read from the disc when loading the savestate. // TODO: It would be possible to create a savestate faster by stopping // the DVD thread regardless of whether there are pending requests. // After loading a savestate, the debug log in FinishRead will report // screwed up times for requests that were submitted before the savestate // was made. Handling that properly may be more effort than it's worth. } void SetDisc(std::unique_ptr<DiscIO::Volume> disc) { WaitUntilIdle(); s_disc = std::move(disc); } bool HasDisc() { return s_disc != nullptr; } bool IsEncryptedAndHashed() { // IsEncryptedAndHashed is thread-safe, so calling WaitUntilIdle isn't necessary. return s_disc->IsEncryptedAndHashed(); } DiscIO::Platform GetDiscType() { // GetVolumeType is thread-safe, so calling WaitUntilIdle isn't necessary. return s_disc->GetVolumeType(); } u64 PartitionOffsetToRawOffset(u64 offset, const DiscIO::Partition& partition) { // PartitionOffsetToRawOffset is thread-safe, so calling WaitUntilIdle isn't necessary. return s_disc->PartitionOffsetToRawOffset(offset, partition); } IOS::ES::TMDReader GetTMD(const DiscIO::Partition& partition) { WaitUntilIdle(); return s_disc->GetTMD(partition); } IOS::ES::TicketReader GetTicket(const DiscIO::Partition& partition) { WaitUntilIdle(); return s_disc->GetTicket(partition); } bool IsInsertedDiscRunning() { if (!s_disc) return false; WaitUntilIdle(); return SConfig::GetInstance().GetGameID() == s_disc->GetGameID(); } bool UpdateRunningGameMetadata(const DiscIO::Partition& partition, std::optional<u64> title_id) { if (!s_disc) return false; WaitUntilIdle(); if (title_id) { const std::optional<u64> volume_title_id = s_disc->GetTitleID(partition); if (!volume_title_id || *volume_title_id != *title_id) return false; } SConfig::GetInstance().SetRunningGameMetadata(*s_disc, partition); return true; } void WaitUntilIdle() { ASSERT(Core::IsCPUThread()); while (!s_request_queue.Empty()) s_result_queue_expanded.Wait(); StopDVDThread(); StartDVDThread(); } void StartRead(u64 dvd_offset, u32 length, const DiscIO::Partition& partition, DVDInterface::ReplyType reply_type, s64 ticks_until_completion) { StartReadInternal(false, 0, dvd_offset, length, partition, reply_type, ticks_until_completion); } void StartReadToEmulatedRAM(u32 output_address, u64 dvd_offset, u32 length, const DiscIO::Partition& partition, DVDInterface::ReplyType reply_type, s64 ticks_until_completion) { StartReadInternal(true, output_address, dvd_offset, length, partition, reply_type, ticks_until_completion); } static void StartReadInternal(bool copy_to_ram, u32 output_address, u64 dvd_offset, u32 length, const DiscIO::Partition& partition, DVDInterface::ReplyType reply_type, s64 ticks_until_completion) { ASSERT(Core::IsCPUThread()); ReadRequest request; request.copy_to_ram = copy_to_ram; request.output_address = output_address; request.dvd_offset = dvd_offset; request.length = length; request.partition = partition; request.reply_type = reply_type; u64 id = s_next_id++; request.id = id; request.time_started_ticks = CoreTiming::GetTicks(); request.realtime_started_us = Common::Timer::GetTimeUs(); s_request_queue.Push(std::move(request)); s_request_queue_expanded.Set(); CoreTiming::ScheduleEvent(ticks_until_completion, s_finish_read, id); } static void FinishRead(u64 id, s64 cycles_late) { // We can't simply pop s_result_queue and always get the ReadResult // we want, because the DVD thread may add ReadResults to the queue // in a different order than we want to get them. What we do instead // is to pop the queue until we find the ReadResult we want (the one // whose ID matches userdata), which means we may end up popping // ReadResults that we don't want. We can't add those unwanted results // back to the queue, because the queue can only have one writer. // Instead, we add them to a map that only is used by the CPU thread. // When this function is called again later, it will check the map for // the wanted ReadResult before it starts searching through the queue. ReadResult result; auto it = s_result_map.find(id); if (it != s_result_map.end()) { result = std::move(it->second); s_result_map.erase(it); } else { while (true) { while (!s_result_queue.Pop(result)) s_result_queue_expanded.Wait(); if (result.first.id == id) break; else s_result_map.emplace(result.first.id, std::move(result)); } } // We have now obtained the right ReadResult. const ReadRequest& request = result.first; const std::vector<u8>& buffer = result.second; DEBUG_LOG(DVDINTERFACE, "Disc has been read. Real time: %" PRIu64 " us. " "Real time including delay: %" PRIu64 " us. " "Emulated time including delay: %" PRIu64 " us.", request.realtime_done_us - request.realtime_started_us, Common::Timer::GetTimeUs() - request.realtime_started_us, (CoreTiming::GetTicks() - request.time_started_ticks) / (SystemTimers::GetTicksPerSecond() / 1000000)); if (buffer.size() != request.length) { PanicAlertT("The disc could not be read (at 0x%" PRIx64 " - 0x%" PRIx64 ").", request.dvd_offset, request.dvd_offset + request.length); } else { if (request.copy_to_ram) Memory::CopyToEmu(request.output_address, buffer.data(), request.length); } // Notify the emulated software that the command has been executed DVDInterface::FinishExecutingCommand(request.reply_type, DVDInterface::INT_TCINT, cycles_late, buffer); } static void DVDThread() { Common::SetCurrentThreadName("DVD thread"); while (true) { s_request_queue_expanded.Wait(); if (s_dvd_thread_exiting.IsSet()) return; ReadRequest request; while (s_request_queue.Pop(request)) { FileMonitor::Log(*s_disc, request.partition, request.dvd_offset); std::vector<u8> buffer(request.length); if (!s_disc->Read(request.dvd_offset, request.length, buffer.data(), request.partition)) buffer.resize(0); request.realtime_done_us = Common::Timer::GetTimeUs(); s_result_queue.Push(ReadResult(std::move(request), std::move(buffer))); s_result_queue_expanded.Set(); if (s_dvd_thread_exiting.IsSet()) return; } } } }
~scoped_thread() { t.join(); }
static void StartDVDThread() { ASSERT(!s_dvd_thread.joinable()); s_dvd_thread_exiting.Clear(); s_dvd_thread = std::thread(DVDThread); }
void CBackupDeleter::waitDone() { if (_thread.joinable()) _thread.join(); }
void join() { internal_thread.join(); }
void joinInputThread() { mShouldBeRunning = false; if( mInputThread.joinable() ) mInputThread.join(); }
void CBackupStatusUpdater::waitDone() { if (_thread.joinable()) _thread.join(); }
std::thread::id get_id() const { return t_.get_id(); }
void join() { t_.join(); s_->propagate_abort(); }
void Flush() { // If already saving state, wait for it to finish if (g_save_thread.joinable()) g_save_thread.join(); }
bool joinable() const { return t_.joinable(); }
namespace State { #if defined(__LZO_STRICT_16BIT) static const u32 IN_LEN = 8 * 1024u; #elif defined(LZO_ARCH_I086) && !defined(LZO_HAVE_MM_HUGE_ARRAY) static const u32 IN_LEN = 60 * 1024u; #else static const u32 IN_LEN = 128 * 1024u; #endif static const u32 OUT_LEN = IN_LEN + (IN_LEN / 16) + 64 + 3; static unsigned char __LZO_MMODEL out[OUT_LEN]; #define HEAP_ALLOC(var, size) \ lzo_align_t __LZO_MMODEL var[((size) + (sizeof(lzo_align_t) - 1)) / sizeof(lzo_align_t)] static HEAP_ALLOC(wrkmem, LZO1X_1_MEM_COMPRESS); static std::string g_last_filename; static CallbackFunc g_onAfterLoadCb = nullptr; // Temporary undo state buffer static std::vector<u8> g_undo_load_buffer; static std::vector<u8> g_current_buffer; static int g_loadDepth = 0; static std::mutex g_cs_undo_load_buffer; static std::mutex g_cs_current_buffer; static Common::Event g_compressAndDumpStateSyncEvent; static std::thread g_save_thread; // Don't forget to increase this after doing changes on the savestate system static const u32 STATE_VERSION = 43; // Last changed in PR 2232 // Maps savestate versions to Dolphin versions. // Versions after 42 don't need to be added to this list, // beacuse they save the exact Dolphin version to savestates. static const std::map<u32, std::pair<std::string, std::string>> s_old_versions = { // The 16 -> 17 change modified the size of StateHeader, // so version older than that can't even be decompressed anymore { 17, { "3.5-1311", "3.5-1364" } }, { 18, { "3.5-1366", "3.5-1371" } }, { 19, { "3.5-1372", "3.5-1408" } }, { 20, { "3.5-1409", "4.0-704" } }, { 21, { "4.0-705", "4.0-889" } }, { 22, { "4.0-905", "4.0-1871" } }, { 23, { "4.0-1873", "4.0-1900" } }, { 24, { "4.0-1902", "4.0-1919" } }, { 25, { "4.0-1921", "4.0-1936" } }, { 26, { "4.0-1939", "4.0-1959" } }, { 27, { "4.0-1961", "4.0-2018" } }, { 28, { "4.0-2020", "4.0-2291" } }, { 29, { "4.0-2293", "4.0-2360" } }, { 30, { "4.0-2362", "4.0-2628" } }, { 31, { "4.0-2632", "4.0-3331" } }, { 32, { "4.0-3334", "4.0-3340" } }, { 33, { "4.0-3342", "4.0-3373" } }, { 34, { "4.0-3376", "4.0-3402" } }, { 35, { "4.0-3409", "4.0-3603" } }, { 36, { "4.0-3610", "4.0-4480" } }, { 37, { "4.0-4484", "4.0-4943" } }, { 38, { "4.0-4963", "4.0-5267" } }, { 39, { "4.0-5279", "4.0-5525" } }, { 40, { "4.0-5531", "4.0-5809" } }, { 41, { "4.0-5811", "4.0-5923" } }, { 42, { "4.0-5925", "4.0-5946" } } }; enum { STATE_NONE = 0, STATE_SAVE = 1, STATE_LOAD = 2, }; static bool g_use_compression = true; void EnableCompression(bool compression) { g_use_compression = compression; } static std::string DoState(PointerWrap& p) { u32 version = STATE_VERSION; { static const u32 COOKIE_BASE = 0xBAADBABE; u32 cookie = version + COOKIE_BASE; p.Do(cookie); version = cookie - COOKIE_BASE; } std::string version_created_by = scm_rev_str; if (version > 42) p.Do(version_created_by); else version_created_by.clear(); if (version != STATE_VERSION) { if (version_created_by.empty() && s_old_versions.count(version)) { // The savestate is from an old version that doesn't // save the Dolphin version number to savestates, but // by looking up the savestate version number, it is possible // to know approximately which Dolphin version was used. std::pair<std::string, std::string> version_range = s_old_versions.find(version)->second; std::string oldest_version = version_range.first; std::string newest_version = version_range.second; version_created_by = "Dolphin " + oldest_version + " - " + newest_version; } // because the version doesn't match, fail. // this will trigger an OSD message like "Can't load state from other revisions" // we could use the version numbers to maintain some level of backward compatibility, but currently don't. p.SetMode(PointerWrap::MODE_MEASURE); return version_created_by; } p.DoMarker("Version"); // Begin with video backend, so that it gets a chance to clear its caches and writeback modified things to RAM g_video_backend->DoState(p); p.DoMarker("video_backend"); if (SConfig::GetInstance().m_LocalCoreStartupParameter.bWii) Wiimote::DoState(p.GetPPtr(), p.GetMode()); p.DoMarker("Wiimote"); PowerPC::DoState(p); p.DoMarker("PowerPC"); HW::DoState(p); p.DoMarker("HW"); CoreTiming::DoState(p); p.DoMarker("CoreTiming"); Movie::DoState(p); p.DoMarker("Movie"); #if defined(HAVE_LIBAV) || defined (WIN32) AVIDump::DoState(); #endif return version_created_by; } void LoadFromBuffer(std::vector<u8>& buffer) { bool wasUnpaused = Core::PauseAndLock(true); u8* ptr = &buffer[0]; PointerWrap p(&ptr, PointerWrap::MODE_READ); DoState(p); Core::PauseAndLock(false, wasUnpaused); } void SaveToBuffer(std::vector<u8>& buffer) { bool wasUnpaused = Core::PauseAndLock(true); u8* ptr = nullptr; PointerWrap p(&ptr, PointerWrap::MODE_MEASURE); DoState(p); const size_t buffer_size = reinterpret_cast<size_t>(ptr); buffer.resize(buffer_size); ptr = &buffer[0]; p.SetMode(PointerWrap::MODE_WRITE); DoState(p); Core::PauseAndLock(false, wasUnpaused); } void VerifyBuffer(std::vector<u8>& buffer) { bool wasUnpaused = Core::PauseAndLock(true); u8* ptr = &buffer[0]; PointerWrap p(&ptr, PointerWrap::MODE_VERIFY); DoState(p); Core::PauseAndLock(false, wasUnpaused); } // return state number not in map static int GetEmptySlot(std::map<double, int> m) { for (int i = 1; i <= (int)NUM_STATES; i++) { bool found = false; for (auto& p : m) { if (p.second == i) { found = true; break; } } if (!found) return i; } return -1; } static std::string MakeStateFilename(int number); // read state timestamps static std::map<double, int> GetSavedStates() { StateHeader header; std::map<double, int> m; for (int i = 1; i <= (int)NUM_STATES; i++) { std::string filename = MakeStateFilename(i); if (File::Exists(filename)) { if (ReadHeader(filename, header)) { double d = Common::Timer::GetDoubleTime() - header.time; // increase time until unique value is obtained while (m.find(d) != m.end()) d += .001; m.insert(std::pair<double,int>(d, i)); } } } return m; } struct CompressAndDumpState_args { std::vector<u8>* buffer_vector; std::mutex* buffer_mutex; std::string filename; bool wait; }; static void CompressAndDumpState(CompressAndDumpState_args save_args) { std::lock_guard<std::mutex> lk(*save_args.buffer_mutex); if (!save_args.wait) g_compressAndDumpStateSyncEvent.Set(); const u8* const buffer_data = &(*(save_args.buffer_vector))[0]; const size_t buffer_size = (save_args.buffer_vector)->size(); std::string& filename = save_args.filename; // For easy debugging Common::SetCurrentThreadName("SaveState thread"); // Moving to last overwritten save-state if (File::Exists(filename)) { if (File::Exists(File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav")) File::Delete((File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav")); if (File::Exists(File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav.dtm")) File::Delete((File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav.dtm")); if (!File::Rename(filename, File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav")) Core::DisplayMessage("Failed to move previous state to state undo backup", 1000); else File::Rename(filename + ".dtm", File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav.dtm"); } if ((Movie::IsMovieActive()) && !Movie::IsJustStartingRecordingInputFromSaveState()) Movie::SaveRecording(filename + ".dtm"); else if (!Movie::IsMovieActive()) File::Delete(filename + ".dtm"); File::IOFile f(filename, "wb"); if (!f) { Core::DisplayMessage("Could not save state", 2000); g_compressAndDumpStateSyncEvent.Set(); return; } // Setting up the header StateHeader header; memcpy(header.gameID, SConfig::GetInstance().m_LocalCoreStartupParameter.GetUniqueID().c_str(), 6); header.size = g_use_compression ? (u32)buffer_size : 0; header.time = Common::Timer::GetDoubleTime(); f.WriteArray(&header, 1); if (header.size != 0) // non-zero header size means the state is compressed { lzo_uint i = 0; while (true) { lzo_uint32 cur_len = 0; lzo_uint out_len = 0; if ((i + IN_LEN) >= buffer_size) { cur_len = (lzo_uint32)(buffer_size - i); } else { cur_len = IN_LEN; } if (lzo1x_1_compress(buffer_data + i, cur_len, out, &out_len, wrkmem) != LZO_E_OK) PanicAlertT("Internal LZO Error - compression failed"); // The size of the data to write is 'out_len' f.WriteArray((lzo_uint32*)&out_len, 1); f.WriteBytes(out, out_len); if (cur_len != IN_LEN) break; i += cur_len; } } else // uncompressed { f.WriteBytes(buffer_data, buffer_size); } Core::DisplayMessage(StringFromFormat("Saved State to %s", filename.c_str()), 2000); g_compressAndDumpStateSyncEvent.Set(); } void SaveAs(const std::string& filename, bool wait) { // Pause the core while we save the state bool wasUnpaused = Core::PauseAndLock(true); // Measure the size of the buffer. u8 *ptr = nullptr; PointerWrap p(&ptr, PointerWrap::MODE_MEASURE); DoState(p); const size_t buffer_size = reinterpret_cast<size_t>(ptr); // Then actually do the write. { std::lock_guard<std::mutex> lk(g_cs_current_buffer); g_current_buffer.resize(buffer_size); ptr = &g_current_buffer[0]; p.SetMode(PointerWrap::MODE_WRITE); DoState(p); } if (p.GetMode() == PointerWrap::MODE_WRITE) { Core::DisplayMessage("Saving State...", 1000); CompressAndDumpState_args save_args; save_args.buffer_vector = &g_current_buffer; save_args.buffer_mutex = &g_cs_current_buffer; save_args.filename = filename; save_args.wait = wait; Flush(); g_save_thread = std::thread(CompressAndDumpState, save_args); g_compressAndDumpStateSyncEvent.Wait(); g_last_filename = filename; } else { // someone aborted the save by changing the mode? Core::DisplayMessage("Unable to save: Internal DoState Error", 4000); } // Resume the core and disable stepping Core::PauseAndLock(false, wasUnpaused); } bool ReadHeader(const std::string& filename, StateHeader& header) { Flush(); File::IOFile f(filename, "rb"); if (!f) { Core::DisplayMessage("State not found", 2000); return false; } f.ReadArray(&header, 1); return true; } static void LoadFileStateData(const std::string& filename, std::vector<u8>& ret_data) { Flush(); File::IOFile f(filename, "rb"); if (!f) { Core::DisplayMessage("State not found", 2000); return; } StateHeader header; f.ReadArray(&header, 1); if (memcmp(SConfig::GetInstance().m_LocalCoreStartupParameter.GetUniqueID().c_str(), header.gameID, 6)) { Core::DisplayMessage(StringFromFormat("State belongs to a different game (ID %.*s)", 6, header.gameID), 2000); return; } std::vector<u8> buffer; if (header.size != 0) // non-zero size means the state is compressed { Core::DisplayMessage("Decompressing State...", 500); buffer.resize(header.size); lzo_uint i = 0; while (true) { lzo_uint32 cur_len = 0; // number of bytes to read lzo_uint new_len = 0; // number of bytes to write if (!f.ReadArray(&cur_len, 1)) break; f.ReadBytes(out, cur_len); const int res = lzo1x_decompress(out, cur_len, &buffer[i], &new_len, nullptr); if (res != LZO_E_OK) { // This doesn't seem to happen anymore. PanicAlertT("Internal LZO Error - decompression failed (%d) (%li, %li) \n" "Try loading the state again", res, i, new_len); return; } i += new_len; } } else // uncompressed { const size_t size = (size_t)(f.GetSize() - sizeof(StateHeader)); buffer.resize(size); if (!f.ReadBytes(&buffer[0], size)) { PanicAlert("wtf? reading bytes: %i", (int)size); return; } } // all good ret_data.swap(buffer); } void LoadAs(const std::string& filename) { if (!Core::IsRunning()) return; // Stop the core while we load the state bool wasUnpaused = Core::PauseAndLock(true); g_loadDepth++; // Save temp buffer for undo load state if (!Movie::IsJustStartingRecordingInputFromSaveState()) { std::lock_guard<std::mutex> lk(g_cs_undo_load_buffer); SaveToBuffer(g_undo_load_buffer); if (Movie::IsMovieActive()) Movie::SaveRecording(File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm"); else if (File::Exists(File::GetUserPath(D_STATESAVES_IDX) +"undo.dtm")) File::Delete(File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm"); } bool loaded = false; bool loadedSuccessfully = false; std::string version_created_by; // brackets here are so buffer gets freed ASAP { std::vector<u8> buffer; LoadFileStateData(filename, buffer); if (!buffer.empty()) { u8 *ptr = &buffer[0]; PointerWrap p(&ptr, PointerWrap::MODE_READ); version_created_by = DoState(p); loaded = true; loadedSuccessfully = (p.GetMode() == PointerWrap::MODE_READ); } } if (loaded) { if (loadedSuccessfully) { Core::DisplayMessage(StringFromFormat("Loaded state from %s", filename.c_str()), 2000); if (File::Exists(filename + ".dtm")) Movie::LoadInput(filename + ".dtm"); else if (!Movie::IsJustStartingRecordingInputFromSaveState() && !Movie::IsJustStartingPlayingInputFromSaveState()) Movie::EndPlayInput(false); } else { // failed to load Core::DisplayMessage("Unable to load: Can't load state from other versions!", 4000); if (!version_created_by.empty()) Core::DisplayMessage("The savestate was created using " + version_created_by, 4000); // since we could be in an inconsistent state now (and might crash or whatever), undo. if (g_loadDepth < 2) UndoLoadState(); } } if (g_onAfterLoadCb) g_onAfterLoadCb(); g_loadDepth--; // resume dat core Core::PauseAndLock(false, wasUnpaused); } void SetOnAfterLoadCallback(CallbackFunc callback) { g_onAfterLoadCb = callback; } void VerifyAt(const std::string& filename) { bool wasUnpaused = Core::PauseAndLock(true); std::vector<u8> buffer; LoadFileStateData(filename, buffer); if (!buffer.empty()) { u8 *ptr = &buffer[0]; PointerWrap p(&ptr, PointerWrap::MODE_VERIFY); DoState(p); if (p.GetMode() == PointerWrap::MODE_VERIFY) Core::DisplayMessage(StringFromFormat("Verified state at %s", filename.c_str()), 2000); else Core::DisplayMessage("Unable to Verify : Can't verify state from other revisions !", 4000); } Core::PauseAndLock(false, wasUnpaused); } void Init() { if (lzo_init() != LZO_E_OK) PanicAlertT("Internal LZO Error - lzo_init() failed"); } void Shutdown() { Flush(); // swapping with an empty vector, rather than clear()ing // this gives a better guarantee to free the allocated memory right NOW (as opposed to, actually, never) { std::lock_guard<std::mutex> lk(g_cs_current_buffer); std::vector<u8>().swap(g_current_buffer); } { std::lock_guard<std::mutex> lk(g_cs_undo_load_buffer); std::vector<u8>().swap(g_undo_load_buffer); } } static std::string MakeStateFilename(int number) { return StringFromFormat("%s%s.s%02i", File::GetUserPath(D_STATESAVES_IDX).c_str(), SConfig::GetInstance().m_LocalCoreStartupParameter.GetUniqueID().c_str(), number); } void Save(int slot, bool wait) { SaveAs(MakeStateFilename(slot), wait); } void Load(int slot) { LoadAs(MakeStateFilename(slot)); } void Verify(int slot) { VerifyAt(MakeStateFilename(slot)); } void LoadLastSaved(int i) { std::map<double, int> savedStates = GetSavedStates(); if (i > (int)savedStates.size()) Core::DisplayMessage("State doesn't exist", 2000); else { std::map<double, int>::iterator it = savedStates.begin(); std::advance(it, i-1); Load(it->second); } } // must wait for state to be written because it must know if all slots are taken void SaveFirstSaved() { std::map<double, int> savedStates = GetSavedStates(); // save to an empty slot if (savedStates.size() < NUM_STATES) Save(GetEmptySlot(savedStates), true); // overwrite the oldest state else { std::map<double, int>::iterator it = savedStates.begin(); std::advance(it, savedStates.size()-1); Save(it->second, true); } } void Flush() { // If already saving state, wait for it to finish if (g_save_thread.joinable()) g_save_thread.join(); } // Load the last state before loading the state void UndoLoadState() { std::lock_guard<std::mutex> lk(g_cs_undo_load_buffer); if (!g_undo_load_buffer.empty()) { if (File::Exists(File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm") || (!Movie::IsMovieActive())) { LoadFromBuffer(g_undo_load_buffer); if (Movie::IsMovieActive()) Movie::LoadInput(File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm"); } else { PanicAlert("No undo.dtm found, aborting undo load state to prevent movie desyncs"); } } else { PanicAlert("There is nothing to undo!"); } } // Load the state that the last save state overwritten on void UndoSaveState() { LoadAs(File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav"); } } // namespace State
static void EmuThreadJoin() { emuThread.join(); emuThread = std::thread(); ILOG("EmuThreadJoin - joined"); }
explicit scoped_thread(std::thread t_): t(std::move(t_)) { if(!t.joinable()) throw std::logic_error("No thread"); }
// Initialize and create emulation thread // Call browser: Init():s_emu_thread(). // See the BootManager.cpp file description for a complete call schedule. void EmuThread() { const SConfig& core_parameter = SConfig::GetInstance(); Common::SetCurrentThreadName("Emuthread - Starting"); if (SConfig::GetInstance().m_OCEnable) DisplayMessage("WARNING: running at non-native CPU clock! Game may not be stable.", 8000); DisplayMessage(cpu_info.brand_string, 8000); DisplayMessage(cpu_info.Summarize(), 8000); DisplayMessage(core_parameter.m_strFilename, 3000); // For a time this acts as the CPU thread... DeclareAsCPUThread(); Movie::Init(); HW::Init(); if (!g_video_backend->Initialize(s_window_handle)) { PanicAlert("Failed to initialize video backend!"); Host_Message(WM_USER_STOP); return; } OSD::AddMessage("Dolphin " + g_video_backend->GetName() + " Video Backend.", 5000); if (cpu_info.HTT) SConfig::GetInstance().bDSPThread = cpu_info.num_cores > 4; else SConfig::GetInstance().bDSPThread = cpu_info.num_cores > 2; if (!DSP::GetDSPEmulator()->Initialize(core_parameter.bWii, core_parameter.bDSPThread)) { HW::Shutdown(); g_video_backend->Shutdown(); PanicAlert("Failed to initialize DSP emulation!"); Host_Message(WM_USER_STOP); return; } bool init_controllers = false; if (!g_controller_interface.IsInit()) { Pad::Initialize(s_window_handle); Keyboard::Initialize(s_window_handle); init_controllers = true; } else { // Update references in case controllers were refreshed Pad::LoadConfig(); Keyboard::LoadConfig(); } // Load and Init Wiimotes - only if we are booting in Wii mode if (core_parameter.bWii) { if (init_controllers) Wiimote::Initialize(s_window_handle, !s_state_filename.empty()); else Wiimote::LoadConfig(); // Activate Wiimotes which don't have source set to "None" for (unsigned int i = 0; i != MAX_BBMOTES; ++i) if (g_wiimote_sources[i]) GetUsbPointer()->AccessWiiMote(i | 0x100)->Activate(true); } AudioCommon::InitSoundStream(); // The hardware is initialized. s_hardware_initialized = true; // Boot to pause or not Core::SetState(core_parameter.bBootToPause ? Core::CORE_PAUSE : Core::CORE_RUN); // Load GCM/DOL/ELF whatever ... we boot with the interpreter core PowerPC::SetMode(PowerPC::MODE_INTERPRETER); CBoot::BootUp(); // Thread is no longer acting as CPU Thread UndeclareAsCPUThread(); // Setup our core, but can't use dynarec if we are compare server if (core_parameter.iCPUCore != PowerPC::CORE_INTERPRETER && (!core_parameter.bRunCompareServer || core_parameter.bRunCompareClient)) { PowerPC::SetMode(PowerPC::MODE_JIT); } else { PowerPC::SetMode(PowerPC::MODE_INTERPRETER); } // Update the window again because all stuff is initialized Host_UpdateDisasmDialog(); Host_UpdateMainFrame(); // Determine the CPU thread function void (*cpuThreadFunc)(void); if (core_parameter.m_BootType == SConfig::BOOT_DFF) cpuThreadFunc = FifoPlayerThread; else cpuThreadFunc = CpuThread; // ENTER THE VIDEO THREAD LOOP if (core_parameter.bCPUThread) { // This thread, after creating the EmuWindow, spawns a CPU // thread, and then takes over and becomes the video thread Common::SetCurrentThreadName("Video thread"); g_video_backend->Video_Prepare(); // Spawn the CPU thread s_cpu_thread = std::thread(cpuThreadFunc); // become the GPU thread g_video_backend->Video_EnterLoop(); // We have now exited the Video Loop INFO_LOG(CONSOLE, "%s", StopMessage(false, "Video Loop Ended").c_str()); } else // SingleCore mode { // The spawned CPU Thread also does the graphics. // The EmuThread is thus an idle thread, which sleeps while // waiting for the program to terminate. Without this extra // thread, the video backend window hangs in single core mode // because no one is pumping messages. Common::SetCurrentThreadName("Emuthread - Idle"); // Spawn the CPU+GPU thread s_cpu_thread = std::thread(cpuThreadFunc); while (PowerPC::GetState() != PowerPC::CPU_POWERDOWN) { g_video_backend->PeekMessages(); Common::SleepCurrentThread(20); } } INFO_LOG(CONSOLE, "%s", StopMessage(true, "Stopping Emu thread ...").c_str()); // Wait for s_cpu_thread to exit INFO_LOG(CONSOLE, "%s", StopMessage(true, "Stopping CPU-GPU thread ...").c_str()); #ifdef USE_GDBSTUB INFO_LOG(CONSOLE, "%s", StopMessage(true, "Stopping GDB ...").c_str()); gdb_deinit(); INFO_LOG(CONSOLE, "%s", StopMessage(true, "GDB stopped.").c_str()); #endif s_cpu_thread.join(); INFO_LOG(CONSOLE, "%s", StopMessage(true, "CPU thread stopped.").c_str()); if (core_parameter.bCPUThread) g_video_backend->Video_Cleanup(); FileMon::Close(); // Stop audio thread - Actually this does nothing when using HLE // emulation, but stops the DSP Interpreter when using LLE emulation. DSP::GetDSPEmulator()->DSP_StopSoundStream(); // We must set up this flag before executing HW::Shutdown() s_hardware_initialized = false; INFO_LOG(CONSOLE, "%s", StopMessage(false, "Shutting down HW").c_str()); HW::Shutdown(); INFO_LOG(CONSOLE, "%s", StopMessage(false, "HW shutdown").c_str()); if (init_controllers) { Wiimote::Shutdown(); Keyboard::Shutdown(); Pad::Shutdown(); init_controllers = false; } g_video_backend->Shutdown(); AudioCommon::ShutdownSoundStream(); INFO_LOG(CONSOLE, "%s", StopMessage(true, "Main Emu thread stopped").c_str()); // Clear on screen messages that haven't expired g_video_backend->Video_ClearMessages(); // Reload sysconf file in order to see changes committed during emulation if (core_parameter.bWii) SConfig::GetInstance().m_SYSCONF->Reload(); INFO_LOG(CONSOLE, "Stop [Video Thread]\t\t---- Shutdown complete ----"); Movie::Shutdown(); PatchEngine::Shutdown(); s_is_stopping = false; if (s_on_stopped_callback) s_on_stopped_callback(); }
void detach() { t_.detach(); }
namespace Core { // TODO: ugly, remove bool g_aspect_wide; bool g_want_determinism; // Declarations and definitions static Common::Timer s_timer; static std::atomic<u32> s_drawn_frame; static std::atomic<u32> s_drawn_video; // Function forwarding void Callback_WiimoteInterruptChannel(int _number, u16 _channelID, const void* _pData, u32 _Size); // Function declarations void EmuThread(); static bool s_is_stopping = false; static bool s_hardware_initialized = false; static bool s_is_started = false; static void* s_window_handle = nullptr; static std::string s_state_filename; static std::thread s_emu_thread; static StoppedCallbackFunc s_on_stopped_callback = nullptr; static std::thread s_cpu_thread; static bool s_request_refresh_info = false; static int s_pause_and_lock_depth = 0; static bool s_is_framelimiter_temp_disabled = false; #ifdef ThreadLocalStorage static ThreadLocalStorage bool tls_is_cpu_thread = false; #else static pthread_key_t s_tls_is_cpu_key; static pthread_once_t s_cpu_key_is_init = PTHREAD_ONCE_INIT; static void InitIsCPUKey() { pthread_key_create(&s_tls_is_cpu_key, nullptr); } #endif bool GetIsFramelimiterTempDisabled() { return s_is_framelimiter_temp_disabled; } void SetIsFramelimiterTempDisabled(bool disable) { s_is_framelimiter_temp_disabled = disable; } std::string GetStateFileName() { return s_state_filename; } void SetStateFileName(const std::string& val) { s_state_filename = val; } void FrameUpdateOnCPUThread() { if (NetPlay::IsNetPlayRunning()) NetPlayClient::SendTimeBase(); } // Display messages and return values // Formatted stop message std::string StopMessage(bool main_thread, const std::string& message) { return StringFromFormat("Stop [%s %i]\t%s\t%s", main_thread ? "Main Thread" : "Video Thread", Common::CurrentThreadId(), MemUsage().c_str(), message.c_str()); } void DisplayMessage(const std::string& message, int time_in_ms) { if (!IsRunning()) return; // Actually displaying non-ASCII could cause things to go pear-shaped for (const char& c : message) { if (!std::isprint(c)) return; } g_video_backend->Video_AddMessage(message, time_in_ms); Host_UpdateTitle(message); } bool IsRunning() { return (GetState() != CORE_UNINITIALIZED || s_hardware_initialized) && !s_is_stopping; } bool IsRunningAndStarted() { return s_is_started && !s_is_stopping; } bool IsRunningInCurrentThread() { return IsRunning() && IsCPUThread(); } bool IsCPUThread() { #ifdef ThreadLocalStorage return tls_is_cpu_thread; #else // Use pthread implementation for Android and Mac // Make sure that s_tls_is_cpu_key is initialized pthread_once(&s_cpu_key_is_init, InitIsCPUKey); return pthread_getspecific(s_tls_is_cpu_key); #endif } bool IsGPUThread() { const SConfig& _CoreParameter = SConfig::GetInstance(); if (_CoreParameter.bCPUThread) { return (s_emu_thread.joinable() && (s_emu_thread.get_id() == std::this_thread::get_id())); } else { return IsCPUThread(); } } // This is called from the GUI thread. See the booting call schedule in // BootManager.cpp bool Init() { const SConfig& _CoreParameter = SConfig::GetInstance(); if (s_emu_thread.joinable()) { if (IsRunning()) { PanicAlertT("Emu Thread already running"); return false; } // The Emu Thread was stopped, synchronize with it. s_emu_thread.join(); } Core::UpdateWantDeterminism(/*initial*/ true); INFO_LOG(OSREPORT, "Starting core = %s mode", _CoreParameter.bWii ? "Wii" : "GameCube"); INFO_LOG(OSREPORT, "CPU Thread separate = %s", _CoreParameter.bCPUThread ? "Yes" : "No"); Host_UpdateMainFrame(); // Disable any menus or buttons at boot g_aspect_wide = _CoreParameter.bWii; if (g_aspect_wide) { IniFile gameIni = _CoreParameter.LoadGameIni(); gameIni.GetOrCreateSection("Wii")->Get("Widescreen", &g_aspect_wide, !!SConfig::GetInstance().m_SYSCONF->GetData<u8>("IPL.AR")); } s_window_handle = Host_GetRenderHandle(); // Start the emu thread s_emu_thread = std::thread(EmuThread); return true; } // Called from GUI thread void Stop() // - Hammertime! { if (GetState() == CORE_STOPPING) return; const SConfig& _CoreParameter = SConfig::GetInstance(); s_is_stopping = true; g_video_backend->EmuStateChange(EMUSTATE_CHANGE_STOP); INFO_LOG(CONSOLE, "Stop [Main Thread]\t\t---- Shutting down ----"); // Stop the CPU INFO_LOG(CONSOLE, "%s", StopMessage(true, "Stop CPU").c_str()); PowerPC::Stop(); // Kick it if it's waiting (code stepping wait loop) CPU::StepOpcode(); if (_CoreParameter.bCPUThread) { // Video_EnterLoop() should now exit so that EmuThread() // will continue concurrently with the rest of the commands // in this function. We no longer rely on Postmessage. INFO_LOG(CONSOLE, "%s", StopMessage(true, "Wait for Video Loop to exit ...").c_str()); g_video_backend->Video_ExitLoop(); } } void DeclareAsCPUThread() { #ifdef ThreadLocalStorage tls_is_cpu_thread = true; #else // Use pthread implementation for Android and Mac // Make sure that s_tls_is_cpu_key is initialized pthread_once(&s_cpu_key_is_init, InitIsCPUKey); pthread_setspecific(s_tls_is_cpu_key, (void*)true); #endif } void UndeclareAsCPUThread() { #ifdef ThreadLocalStorage tls_is_cpu_thread = false; #else // Use pthread implementation for Android and Mac // Make sure that s_tls_is_cpu_key is initialized pthread_once(&s_cpu_key_is_init, InitIsCPUKey); pthread_setspecific(s_tls_is_cpu_key, (void*)false); #endif } // Create the CPU thread, which is a CPU + Video thread in Single Core mode. static void CpuThread() { DeclareAsCPUThread(); const SConfig& _CoreParameter = SConfig::GetInstance(); if (_CoreParameter.bCPUThread) { Common::SetCurrentThreadName("CPU thread"); } else { Common::SetCurrentThreadName("CPU-GPU thread"); g_video_backend->Video_Prepare(); } if (_CoreParameter.bFastmem) EMM::InstallExceptionHandler(); // Let's run under memory watch if (!s_state_filename.empty()) State::LoadAs(s_state_filename); s_is_started = true; #ifdef USE_GDBSTUB #ifndef _WIN32 if (!_CoreParameter.gdb_socket.empty()) { gdb_init_local(_CoreParameter.gdb_socket.data()); gdb_break(); } else #endif if (_CoreParameter.iGDBPort > 0) { gdb_init(_CoreParameter.iGDBPort); // break at next instruction (the first instruction) gdb_break(); } #endif // Enter CPU run loop. When we leave it - we are done. CPU::Run(); s_is_started = false; if (!_CoreParameter.bCPUThread) g_video_backend->Video_Cleanup(); if (_CoreParameter.bFastmem) EMM::UninstallExceptionHandler(); return; } static void FifoPlayerThread() { const SConfig& _CoreParameter = SConfig::GetInstance(); if (_CoreParameter.bCPUThread) { Common::SetCurrentThreadName("FIFO player thread"); } else { g_video_backend->Video_Prepare(); Common::SetCurrentThreadName("FIFO-GPU thread"); } s_is_started = true; DeclareAsCPUThread(); // Enter CPU run loop. When we leave it - we are done. if (FifoPlayer::GetInstance().Open(_CoreParameter.m_strFilename)) { FifoPlayer::GetInstance().Play(); FifoPlayer::GetInstance().Close(); } UndeclareAsCPUThread(); s_is_started = false; if (!_CoreParameter.bCPUThread) g_video_backend->Video_Cleanup(); return; } // Initialize and create emulation thread // Call browser: Init():s_emu_thread(). // See the BootManager.cpp file description for a complete call schedule. void EmuThread() { const SConfig& core_parameter = SConfig::GetInstance(); Common::SetCurrentThreadName("Emuthread - Starting"); if (SConfig::GetInstance().m_OCEnable) DisplayMessage("WARNING: running at non-native CPU clock! Game may not be stable.", 8000); DisplayMessage(cpu_info.brand_string, 8000); DisplayMessage(cpu_info.Summarize(), 8000); DisplayMessage(core_parameter.m_strFilename, 3000); // For a time this acts as the CPU thread... DeclareAsCPUThread(); Movie::Init(); HW::Init(); if (!g_video_backend->Initialize(s_window_handle)) { PanicAlert("Failed to initialize video backend!"); Host_Message(WM_USER_STOP); return; } OSD::AddMessage("Dolphin " + g_video_backend->GetName() + " Video Backend.", 5000); if (cpu_info.HTT) SConfig::GetInstance().bDSPThread = cpu_info.num_cores > 4; else SConfig::GetInstance().bDSPThread = cpu_info.num_cores > 2; if (!DSP::GetDSPEmulator()->Initialize(core_parameter.bWii, core_parameter.bDSPThread)) { HW::Shutdown(); g_video_backend->Shutdown(); PanicAlert("Failed to initialize DSP emulation!"); Host_Message(WM_USER_STOP); return; } bool init_controllers = false; if (!g_controller_interface.IsInit()) { Pad::Initialize(s_window_handle); Keyboard::Initialize(s_window_handle); init_controllers = true; } else { // Update references in case controllers were refreshed Pad::LoadConfig(); Keyboard::LoadConfig(); } // Load and Init Wiimotes - only if we are booting in Wii mode if (core_parameter.bWii) { if (init_controllers) Wiimote::Initialize(s_window_handle, !s_state_filename.empty()); else Wiimote::LoadConfig(); // Activate Wiimotes which don't have source set to "None" for (unsigned int i = 0; i != MAX_BBMOTES; ++i) if (g_wiimote_sources[i]) GetUsbPointer()->AccessWiiMote(i | 0x100)->Activate(true); } AudioCommon::InitSoundStream(); // The hardware is initialized. s_hardware_initialized = true; // Boot to pause or not Core::SetState(core_parameter.bBootToPause ? Core::CORE_PAUSE : Core::CORE_RUN); // Load GCM/DOL/ELF whatever ... we boot with the interpreter core PowerPC::SetMode(PowerPC::MODE_INTERPRETER); CBoot::BootUp(); // Thread is no longer acting as CPU Thread UndeclareAsCPUThread(); // Setup our core, but can't use dynarec if we are compare server if (core_parameter.iCPUCore != PowerPC::CORE_INTERPRETER && (!core_parameter.bRunCompareServer || core_parameter.bRunCompareClient)) { PowerPC::SetMode(PowerPC::MODE_JIT); } else { PowerPC::SetMode(PowerPC::MODE_INTERPRETER); } // Update the window again because all stuff is initialized Host_UpdateDisasmDialog(); Host_UpdateMainFrame(); // Determine the CPU thread function void (*cpuThreadFunc)(void); if (core_parameter.m_BootType == SConfig::BOOT_DFF) cpuThreadFunc = FifoPlayerThread; else cpuThreadFunc = CpuThread; // ENTER THE VIDEO THREAD LOOP if (core_parameter.bCPUThread) { // This thread, after creating the EmuWindow, spawns a CPU // thread, and then takes over and becomes the video thread Common::SetCurrentThreadName("Video thread"); g_video_backend->Video_Prepare(); // Spawn the CPU thread s_cpu_thread = std::thread(cpuThreadFunc); // become the GPU thread g_video_backend->Video_EnterLoop(); // We have now exited the Video Loop INFO_LOG(CONSOLE, "%s", StopMessage(false, "Video Loop Ended").c_str()); } else // SingleCore mode { // The spawned CPU Thread also does the graphics. // The EmuThread is thus an idle thread, which sleeps while // waiting for the program to terminate. Without this extra // thread, the video backend window hangs in single core mode // because no one is pumping messages. Common::SetCurrentThreadName("Emuthread - Idle"); // Spawn the CPU+GPU thread s_cpu_thread = std::thread(cpuThreadFunc); while (PowerPC::GetState() != PowerPC::CPU_POWERDOWN) { g_video_backend->PeekMessages(); Common::SleepCurrentThread(20); } } INFO_LOG(CONSOLE, "%s", StopMessage(true, "Stopping Emu thread ...").c_str()); // Wait for s_cpu_thread to exit INFO_LOG(CONSOLE, "%s", StopMessage(true, "Stopping CPU-GPU thread ...").c_str()); #ifdef USE_GDBSTUB INFO_LOG(CONSOLE, "%s", StopMessage(true, "Stopping GDB ...").c_str()); gdb_deinit(); INFO_LOG(CONSOLE, "%s", StopMessage(true, "GDB stopped.").c_str()); #endif s_cpu_thread.join(); INFO_LOG(CONSOLE, "%s", StopMessage(true, "CPU thread stopped.").c_str()); if (core_parameter.bCPUThread) g_video_backend->Video_Cleanup(); FileMon::Close(); // Stop audio thread - Actually this does nothing when using HLE // emulation, but stops the DSP Interpreter when using LLE emulation. DSP::GetDSPEmulator()->DSP_StopSoundStream(); // We must set up this flag before executing HW::Shutdown() s_hardware_initialized = false; INFO_LOG(CONSOLE, "%s", StopMessage(false, "Shutting down HW").c_str()); HW::Shutdown(); INFO_LOG(CONSOLE, "%s", StopMessage(false, "HW shutdown").c_str()); if (init_controllers) { Wiimote::Shutdown(); Keyboard::Shutdown(); Pad::Shutdown(); init_controllers = false; } g_video_backend->Shutdown(); AudioCommon::ShutdownSoundStream(); INFO_LOG(CONSOLE, "%s", StopMessage(true, "Main Emu thread stopped").c_str()); // Clear on screen messages that haven't expired g_video_backend->Video_ClearMessages(); // Reload sysconf file in order to see changes committed during emulation if (core_parameter.bWii) SConfig::GetInstance().m_SYSCONF->Reload(); INFO_LOG(CONSOLE, "Stop [Video Thread]\t\t---- Shutdown complete ----"); Movie::Shutdown(); PatchEngine::Shutdown(); s_is_stopping = false; if (s_on_stopped_callback) s_on_stopped_callback(); } // Set or get the running state void SetState(EState _State) { switch (_State) { case CORE_PAUSE: CPU::EnableStepping(true); // Break Wiimote::Pause(); break; case CORE_RUN: CPU::EnableStepping(false); Wiimote::Resume(); break; default: PanicAlert("Invalid state"); break; } } EState GetState() { if (s_is_stopping) return CORE_STOPPING; if (s_hardware_initialized) { if (CPU::IsStepping()) return CORE_PAUSE; return CORE_RUN; } return CORE_UNINITIALIZED; } static std::string GenerateScreenshotFolderPath() { const std::string& gameId = SConfig::GetInstance().GetUniqueID(); std::string path = File::GetUserPath(D_SCREENSHOTS_IDX) + gameId + DIR_SEP_CHR; if (!File::CreateFullPath(path)) { // fallback to old-style screenshots, without folder. path = File::GetUserPath(D_SCREENSHOTS_IDX); } return path; } static std::string GenerateScreenshotName() { std::string path = GenerateScreenshotFolderPath(); //append gameId, path only contains the folder here. path += SConfig::GetInstance().GetUniqueID(); std::string name; for (int i = 1; File::Exists(name = StringFromFormat("%s-%d.png", path.c_str(), i)); ++i) { // TODO? } return name; } void SaveScreenShot() { const bool bPaused = (GetState() == CORE_PAUSE); SetState(CORE_PAUSE); g_video_backend->Video_Screenshot(GenerateScreenshotName()); if (!bPaused) SetState(CORE_RUN); } void SaveScreenShot(const std::string& name) { const bool bPaused = (GetState() == CORE_PAUSE); SetState(CORE_PAUSE); std::string filePath = GenerateScreenshotFolderPath() + name + ".png"; g_video_backend->Video_Screenshot(filePath); if (!bPaused) SetState(CORE_RUN); } void RequestRefreshInfo() { s_request_refresh_info = true; } bool PauseAndLock(bool doLock, bool unpauseOnUnlock) { if (!IsRunning()) return true; // let's support recursive locking to simplify things on the caller's side, // and let's do it at this outer level in case the individual systems don't support it. if (doLock ? s_pause_and_lock_depth++ : --s_pause_and_lock_depth) return true; // first pause or unpause the CPU bool wasUnpaused = CPU::PauseAndLock(doLock, unpauseOnUnlock); ExpansionInterface::PauseAndLock(doLock, unpauseOnUnlock); // audio has to come after CPU, because CPU thread can wait for audio thread (m_throttle). DSP::GetDSPEmulator()->PauseAndLock(doLock, unpauseOnUnlock); // video has to come after CPU, because CPU thread can wait for video thread (s_efbAccessRequested). g_video_backend->PauseAndLock(doLock, unpauseOnUnlock); return wasUnpaused; } // Apply Frame Limit and Display FPS info // This should only be called from VI void VideoThrottle() { // Update info per second u32 ElapseTime = (u32)s_timer.GetTimeDifference(); if ((ElapseTime >= 1000 && s_drawn_video.load() > 0) || s_request_refresh_info) { UpdateTitle(); // Reset counter s_timer.Update(); s_drawn_frame.store(0); s_drawn_video.store(0); } s_drawn_video++; } // Executed from GPU thread // reports if a frame should be skipped or not // depending on the framelimit set bool ShouldSkipFrame(int skipped) { const u32 TargetFPS = (SConfig::GetInstance().m_Framelimit > 1) ? (SConfig::GetInstance().m_Framelimit - 1) * 5 : VideoInterface::TargetRefreshRate; const u32 frames = s_drawn_frame.load(); const bool fps_slow = !(s_timer.GetTimeDifference() < (frames + skipped) * 1000 / TargetFPS); return fps_slow; } // --- Callbacks for backends / engine --- // Should be called from GPU thread when a frame is drawn void Callback_VideoCopiedToXFB(bool video_update) { if (video_update) s_drawn_frame++; Movie::FrameUpdate(); } void UpdateTitle() { u32 ElapseTime = (u32)s_timer.GetTimeDifference(); s_request_refresh_info = false; SConfig& _CoreParameter = SConfig::GetInstance(); if (ElapseTime == 0) ElapseTime = 1; float FPS = (float)(s_drawn_frame.load() * 1000.0 / ElapseTime); float VPS = (float)(s_drawn_video.load() * 1000.0 / ElapseTime); float Speed = (float)(s_drawn_video.load() * (100 * 1000.0) / (VideoInterface::TargetRefreshRate * ElapseTime)); // Settings are shown the same for both extended and summary info std::string SSettings = StringFromFormat("%s %s | %s | %s", cpu_core_base->GetName(), _CoreParameter.bCPUThread ? "DC" : "SC", g_video_backend->GetDisplayName().c_str(), _CoreParameter.bDSPHLE ? "HLE" : "LLE"); std::string SFPS; if (Movie::IsPlayingInput()) SFPS = StringFromFormat("VI: %u/%u - Input: %u/%u - FPS: %.0f - VPS: %.0f - %.0f%%", (u32)Movie::g_currentFrame, (u32)Movie::g_totalFrames, (u32)Movie::g_currentInputCount, (u32)Movie::g_totalInputCount, FPS, VPS, Speed); else if (Movie::IsRecordingInput()) SFPS = StringFromFormat("VI: %u - Input: %u - FPS: %.0f - VPS: %.0f - %.0f%%", (u32)Movie::g_currentFrame, (u32)Movie::g_currentInputCount, FPS, VPS, Speed); else { SFPS = StringFromFormat("FPS: %.0f - VPS: %.0f - %.0f%%", FPS, VPS, Speed); if (SConfig::GetInstance().m_InterfaceExtendedFPSInfo) { // Use extended or summary information. The summary information does not print the ticks data, // that's more of a debugging interest, it can always be optional of course if someone is interested. static u64 ticks = 0; static u64 idleTicks = 0; u64 newTicks = CoreTiming::GetTicks(); u64 newIdleTicks = CoreTiming::GetIdleTicks(); u64 diff = (newTicks - ticks) / 1000000; u64 idleDiff = (newIdleTicks - idleTicks) / 1000000; ticks = newTicks; idleTicks = newIdleTicks; float TicksPercentage = (float)diff / (float)(SystemTimers::GetTicksPerSecond() / 1000000) * 100; SFPS += StringFromFormat(" | CPU: %s%i MHz [Real: %i + IdleSkip: %i] / %i MHz (%s%3.0f%%)", _CoreParameter.bSkipIdle ? "~" : "", (int)(diff), (int)(diff - idleDiff), (int)(idleDiff), SystemTimers::GetTicksPerSecond() / 1000000, _CoreParameter.bSkipIdle ? "~" : "", TicksPercentage); } } // This is our final "frame counter" string std::string SMessage = StringFromFormat("%s | %s", SSettings.c_str(), SFPS.c_str()); // Update the audio timestretcher with the current speed if (g_sound_stream) { CMixer* pMixer = g_sound_stream->GetMixer(); pMixer->UpdateSpeed((float)Speed / 100); } Host_UpdateTitle(SMessage); } void Shutdown() { // During shutdown DXGI expects us to handle some messages on the UI thread. // Therefore we can't immediately block and wait for the emu thread to shut // down, so we join the emu thread as late as possible when the UI has already // shut down. // For more info read "DirectX Graphics Infrastructure (DXGI): Best Practices" // on MSDN. if (s_emu_thread.joinable()) s_emu_thread.join(); } void SetOnStoppedCallback(StoppedCallbackFunc callback) { s_on_stopped_callback = callback; } void UpdateWantDeterminism(bool initial) { // For now, this value is not itself configurable. Instead, individual // settings that depend on it, such as GPU determinism mode. should have // override options for testing, bool new_want_determinism = Movie::IsPlayingInput() || Movie::IsRecordingInput() || NetPlay::IsNetPlayRunning(); if (new_want_determinism != g_want_determinism || initial) { WARN_LOG(COMMON, "Want determinism <- %s", new_want_determinism ? "true" : "false"); bool was_unpaused = Core::PauseAndLock(true); g_want_determinism = new_want_determinism; WiiSockMan::GetInstance().UpdateWantDeterminism(new_want_determinism); g_video_backend->UpdateWantDeterminism(new_want_determinism); // We need to clear the cache because some parts of the JIT depend on want_determinism, e.g. use of FMA. JitInterface::ClearCache(); Common::InitializeWiiRoot(g_want_determinism); Core::PauseAndLock(false, was_unpaused); } } } // Core
int main(int argc, char *argv[]){ unsigned char stop, temp; unsigned char cmd; unsigned char arg1, arg2; char arg1buf[4], arg2buf[4]; packet recievepkt; packet sendpkt; int fd_joy; // File Descriptor for joystick int temp1; struct js_event e; temp1 = 0; // Set first time stamp to zero fd_joy = open_joystick(); // /Initalize the joystick // printf("fd_joy: %d\n", fd_joy); strncpy(device, argv[1], DEVICE_MAX_SIZE); //set up serial port if((fd = configDevice()) < 2){ perror("Error opening/configuring device\n"); return -1; } menu(); //set handler for close command signal(SIGUSR2, catchClose); signal(SIGTERM, catchClose); //create timer for ping pid = fork(); if (pid < 0) printf("Error creating ping timer process\n"); else if(pid == 0){ //die if parent dies prctl(PR_SET_PDEATHSIG, SIGHUP); //set alarm for ping timeout signal(SIGALRM, sendPing); //prime Timeout reset handler signal(SIGUSR1, catchTimeoutReset); alarm(PING_INTERVAL); for(timeouts = 0; timeouts < PING_RETRIES;){ //spin wheels. exit if ping timeout occurs } //kill parent process if ping timeout printf("Ping timeout. %d unanswered pings\n", timeouts); //tell parent to exit kill(getppid(), SIGKILL); return 1; } int readcount; char zeroflag; //main control loop for(stop = 0; stop <= 0;){ //cmd = 'V'; //arg1 = 0; //arg2 = 0; /*scanf("%c %s %s%c", &cmd, arg1buf, arg2buf, NULL); //get command //strcpy(arg1buf, "Z"); arg1 = (unsigned char)atoi(arg1buf); arg2 = (unsigned char)atoi(arg2buf); if(arg1 == 0){ arg1 = 1; } if(arg2 == 0){ arg2 = 1; }*/ readcount = read (fd_joy, &e, sizeof(e)); //printf("servo: %d\r", e.time); //printf("bytes read from joystick: %d\n", readcount); //if(e.time > temp1) { //printf("Bryan\n"); if(e.type == 1 && e.value == 1){ if(e.time > temp1){ button_press(e.number); temp1 = e.time; } } if(e.type == 2 /*&& e.value != 0*/){ if(!zeroflag){ axis_press(e.number, e.value); } //set zeroflag if event input zero //necessary to not keep resending packages //when in the zero (stopped) position zeroflag = (e.value == 0) ? 1 : 0; //printf("zeroflag %c\n", zeroflag); } } //while((temp = getchar()) != '\n' && temp != EOF); //clear buffer //printf("CMD: %c ARG1: %c ARG2: %c\n", cmd, arg1, arg2); //stop = parseCommand(cmd, arg1, arg2); } close(fd); //tell child to exit kill(pid, SIGUSR2); t1.join(); scanf("", NULL); return 0; }
~LedgerCleanerImp () override { if (thread_.joinable()) LogicError ("LedgerCleanerImp::onStop not called."); }
void Server::join() { serverThread_.join(); }
void CBackupDeleter::start() { assert(!_thread.joinable()); _thread= std::thread( &CBackupDeleter::run, this); }
void GroupNodeImpl::join() { event_thread_->join(); }
void CMySourceParser::waitDone() { if (_thread.joinable()) _thread.join(); }
void join(std::thread &t) { t.join(); }
void detach() { internal_thread.detach(); }
~concurrent() { q.push([=]{ done=true; }); thd.join(); }