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; } } }
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); }
void MoveEvents() { for (Event ev; s_ts_queue.Pop(ev);) { ev.fifo_order = s_event_fifo_id++; s_event_queue.emplace_back(std::move(ev)); std::push_heap(s_event_queue.begin(), s_event_queue.end(), std::greater<Event>()); } }
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. }