diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.cpp index f1aa544f7d..fc034a9fdb 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.cpp @@ -1165,8 +1165,8 @@ void CEXISlippi::prepareFrameData(u8* payload) // data from this frame. Don't wait until next frame is processing is complete // (this is the last frame, in that case) auto isFrameFound = m_current_game->DoesFrameExist(frameIndex); - g_playbackStatus->latestFrame = m_current_game->GetLatestIndex(); - auto isNextFrameFound = g_playbackStatus->latestFrame > frameIndex; + g_playbackStatus->lastFrame = m_current_game->GetLatestIndex(); + auto isNextFrameFound = g_playbackStatus->lastFrame > frameIndex; auto isFrameComplete = checkFrameFullyFetched(frameIndex); auto isFrameReady = isFrameFound && (isProcessingComplete || isNextFrameFound || isFrameComplete); @@ -1199,8 +1199,8 @@ void CEXISlippi::prepareFrameData(u8* payload) } // If RealTimeMode is enabled, let's trigger fast forwarding under certain conditions - auto isFarBehind = g_playbackStatus->latestFrame - frameIndex > 2; - auto isVeryFarBehind = g_playbackStatus->latestFrame - frameIndex > 25; + auto isFarBehind = g_playbackStatus->lastFrame - frameIndex > 2; + auto isVeryFarBehind = g_playbackStatus->lastFrame - frameIndex > 25; if (isFarBehind && commSettings.mode == "mirror" && commSettings.isRealTimeMode) { g_playbackStatus->isSoftFFW = true; @@ -1211,7 +1211,7 @@ void CEXISlippi::prepareFrameData(u8* payload) g_playbackStatus->isHardFFW = isVeryFarBehind; } - if (g_playbackStatus->latestFrame == frameIndex) + if (g_playbackStatus->lastFrame == frameIndex) { // The reason to disable fast forwarding here is in hopes // of disabling it on the last frame that we have actually received. @@ -1257,14 +1257,11 @@ void CEXISlippi::prepareFrameData(u8* payload) rollbackCode = 1; } - // WARN_LOG(EXPANSIONINTERFACE, "[Frame %d] Playback current behind by: %d frames.", frameIndex, - // g_playbackStatus->latestFrame - frameIndex); - // Keep track of last FFW frame, used for soft FFW's if (shouldFFW) { - WARN_LOG(EXPANSIONINTERFACE, "[Frame %d] FFW frame, behind by: %d frames.", frameIndex, - g_playbackStatus->latestFrame - frameIndex); + WARN_LOG(SLIPPI, "[Frame %d] FFW frame, behind by: %d frames.", frameIndex, + g_playbackStatus->lastFrame - frameIndex); g_playbackStatus->lastFFWFrame = frameIndex; } @@ -1291,23 +1288,6 @@ void CEXISlippi::prepareFrameData(u8* payload) frameSeqIdx += 1; } - // else - //{ - // std::vector fakePayload(8, 0); - // *(s32 *)(&fakePayload[0]) = Common::swap32(frame->frame); - - // if (frame->frame == 400) - // { - // handleCaptureSavestate(&fakePayload[0]); - // } - - // if (frame->frame == 950) - // { - // *(s32 *)(&fakePayload[0]) = Common::swap32(400); - // handleLoadSavestate(&fakePayload[0]); - // handleCaptureSavestate(&fakePayload[0]); - // } - //} // For normal replays, modify slippi seek/playback data as needed // TODO: maybe handle other modes too? diff --git a/Source/Core/Core/Slippi/SlippiPlayback.cpp b/Source/Core/Core/Slippi/SlippiPlayback.cpp index 7ca9074a80..b7a2bd392a 100644 --- a/Source/Core/Core/Slippi/SlippiPlayback.cpp +++ b/Source/Core/Core/Slippi/SlippiPlayback.cpp @@ -21,6 +21,7 @@ extern std::unique_ptr g_replayComm; static std::mutex mtx; static std::mutex seekMtx; +static std::mutex ffwMtx; static std::mutex diffMtx; static std::unique_lock processingLock(diffMtx); static std::condition_variable condVar; @@ -61,14 +62,13 @@ SlippiPlaybackStatus::SlippiPlaybackStatus() lastFFWFrame = INT_MIN; currentPlaybackFrame = INT_MIN; targetFrameNum = INT_MAX; - latestFrame = Slippi::GAME_FIRST_FRAME; + lastFrame = Slippi::PLAYBACK_FIRST_SAVE; } void SlippiPlaybackStatus::startThreads() { shouldRunThreads = true; m_savestateThread = std::thread(&SlippiPlaybackStatus::SavestateThread, this); - m_seekThread = std::thread(&SlippiPlaybackStatus::SeekThread, this); } void SlippiPlaybackStatus::prepareSlippiPlayback(s32& frameIndex) @@ -113,9 +113,6 @@ void SlippiPlaybackStatus::resetPlayback() if (m_savestateThread.joinable()) m_savestateThread.detach(); - if (m_seekThread.joinable()) - m_seekThread.detach(); - condVar.notify_one(); // Will allow thread to kill itself futureDiffs.clear(); futureDiffs.rehash(0); @@ -178,96 +175,66 @@ void SlippiPlaybackStatus::SavestateThread() INFO_LOG(SLIPPI, "Exiting savestate thread"); } -void SlippiPlaybackStatus::SeekThread() +void SlippiPlaybackStatus::SeekToFrame(s32 frameNum) { - Common::SetCurrentThreadName("Seek thread"); - std::unique_lock seekLock(seekMtx); - - INFO_LOG(SLIPPI, "Entering seek thread"); - - while (shouldRunThreads) - { - bool shouldSeek = inSlippiPlayback && (shouldJumpBack || shouldJumpForward || targetFrameNum != INT_MAX); - - if (shouldSeek) - { - auto replayCommSettings = g_replayComm->getSettings(); - if (replayCommSettings.mode == "queue") - clearWatchSettingsStartEnd(); - - bool paused = (Core::GetState() == Core::State::Paused); - Core::SetState(Core::State::Paused); - - u32 jumpInterval = 300; // 5 seconds; - - if (shouldJumpForward) - targetFrameNum = currentPlaybackFrame + jumpInterval; - - if (shouldJumpBack) - targetFrameNum = currentPlaybackFrame - jumpInterval; - - // Handle edgecases for trying to seek before start or past end of game - if (targetFrameNum < Slippi::PLAYBACK_FIRST_SAVE) - targetFrameNum = Slippi::PLAYBACK_FIRST_SAVE; - - if (targetFrameNum > latestFrame) - { - targetFrameNum = latestFrame; - } - - s32 closestStateFrame = targetFrameNum - emod(targetFrameNum - Slippi::PLAYBACK_FIRST_SAVE, FRAME_INTERVAL); - - bool isLoadingStateOptimal = - targetFrameNum < currentPlaybackFrame || closestStateFrame > currentPlaybackFrame; - - if (isLoadingStateOptimal) - { - if (closestStateFrame <= Slippi::PLAYBACK_FIRST_SAVE) - { - State::LoadFromBuffer(iState); - } - else - { - // If this diff has been processed, load it - if (futureDiffs.count(closestStateFrame) > 0) - { - std::string stateString; - decoder.Decode((char*)iState.data(), iState.size(), futureDiffs[closestStateFrame].get(), - &stateString); - std::vector stateToLoad(stateString.begin(), stateString.end()); - State::LoadFromBuffer(stateToLoad); - }; - } - } - - // Fastforward until we get to the frame we want - if (targetFrameNum != closestStateFrame && targetFrameNum != latestFrame) - { - isHardFFW = true; - SConfig::GetInstance().m_OCEnable = true; - SConfig::GetInstance().m_OCFactor = 4.0f; - - Core::SetState(Core::State::Running); - cv_waitingForTargetFrame.wait(seekLock); - Core::SetState(Core::State::Paused); - - SConfig::GetInstance().m_OCFactor = 1.0f; - SConfig::GetInstance().m_OCEnable = false; - isHardFFW = false; - } - - if (!paused) - Core::SetState(Core::State::Running); - - shouldJumpBack = false; - shouldJumpForward = false; - targetFrameNum = INT_MAX; + if (seekMtx.try_lock()) { + if (frameNum < Slippi::PLAYBACK_FIRST_SAVE || frameNum > lastFrame) { + INFO_LOG(SLIPPI, "Error: Invalid seek to frame: %d", frameNum); + seekMtx.unlock(); + return; } - Common::SleepCurrentThread(SLEEP_TIME_MS); - } + std::unique_lock ffwLock(ffwMtx); + auto replayCommSettings = g_replayComm->getSettings(); + if (replayCommSettings.mode == "queue") + clearWatchSettingsStartEnd(); - INFO_LOG(SLIPPI, "Exit seek thread"); + auto prevState = Core::GetState(); + if (prevState != Core::State::Paused) + Core::SetState(Core::State::Paused); + + s32 closestStateFrame = frameNum - emod(frameNum - Slippi::PLAYBACK_FIRST_SAVE, FRAME_INTERVAL); + bool isLoadingStateOptimal = frameNum < currentPlaybackFrame || closestStateFrame > currentPlaybackFrame; + if (isLoadingStateOptimal) + { + if (closestStateFrame <= Slippi::PLAYBACK_FIRST_SAVE) + { + State::LoadFromBuffer(iState); + } + else + { + // If this diff has been processed, load it + if (futureDiffs.count(closestStateFrame) > 0) + { + std::string stateString; + decoder.Decode((char*)iState.data(), iState.size(), futureDiffs[closestStateFrame].get(), + &stateString); + std::vector stateToLoad(stateString.begin(), stateString.end()); + State::LoadFromBuffer(stateToLoad); + }; + } + } + + // Fastforward until we get to the frame we want + if (frameNum != closestStateFrame && frameNum != lastFrame) + { + isHardFFW = true; + SConfig::GetInstance().m_OCEnable = true; + SConfig::GetInstance().m_OCFactor = 4.0f; + + Core::SetState(Core::State::Running); + cv_waitingForTargetFrame.wait(ffwLock); + Core::SetState(Core::State::Paused); + + SConfig::GetInstance().m_OCFactor = 1.0f; + SConfig::GetInstance().m_OCEnable = false; + isHardFFW = false; + } + + Core::SetState(prevState); + } else { + INFO_LOG(SLIPPI, "Already seeking. Ignoring this call"); + } } void SlippiPlaybackStatus::clearWatchSettingsStartEnd() diff --git a/Source/Core/Core/Slippi/SlippiPlayback.h b/Source/Core/Core/Slippi/SlippiPlayback.h index 085f94f8e8..52d781a06d 100644 --- a/Source/Core/Core/Slippi/SlippiPlayback.h +++ b/Source/Core/Core/Slippi/SlippiPlayback.h @@ -25,10 +25,9 @@ public: s32 lastFFWFrame = INT_MIN; s32 currentPlaybackFrame = INT_MIN; s32 targetFrameNum = INT_MAX; - s32 latestFrame = Slippi::GAME_FIRST_FRAME; + s32 lastFrame = Slippi::PLAYBACK_FIRST_SAVE; std::thread m_savestateThread; - std::thread m_seekThread; void startThreads(void); void resetPlayback(void); @@ -36,7 +35,7 @@ public: private: void SavestateThread(void); - void SeekThread(void); + void SeekToFrame(s32 targetFrameNum); void processInitialState(); void clearWatchSettingsStartEnd(); diff --git a/Source/Core/VideoCommon/OnScreenDisplay.cpp b/Source/Core/VideoCommon/OnScreenDisplay.cpp index 71115e733b..bc4abdbcee 100644 --- a/Source/Core/VideoCommon/OnScreenDisplay.cpp +++ b/Source/Core/VideoCommon/OnScreenDisplay.cpp @@ -169,9 +169,9 @@ bool ButtonCustom(const char* label, const ImVec2& size_arg, ImGuiButtonFlags fl ImGui::RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); if (hovered || held) - ImGui::RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb, ImVec4(0.9f, 0.9f, 0.9f, 1.0f)); + ImGui::RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb, ImVec4(0.9f, 0.9f, 0.9f, style.Alpha)); else - ImGui::RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb, ImVec4(0.9f, 0.9f, 0.9f, 0.6f)); + ImGui::RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb, ImVec4(0.9f, 0.9f, 0.9f, 0.6f * style.Alpha)); // Automatically close popups //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) @@ -210,8 +210,14 @@ bool SliderCustomBehavior(const ImRect& bb, ImGuiID id, int* v, int v_min, int v bool isActive = g.ActiveId == id; static bool isHeld = false; const bool hovered = ImGui::ItemHoverable(bb, id); - if (isHeld) + if (isHeld) { isHeld = isHeld && isDown; + // If no longer held, slider was let go. Trigger mark edited + if (!isHeld) { + INFO_LOG(SLIPPI, "Do seek here!"); + ImGui::MarkItemEdited(id); // TODO only mark on mouse up + } + } else isHeld = hovered && isDown; @@ -257,7 +263,6 @@ bool SliderCustomBehavior(const ImRect& bb, ImGuiID id, int* v, int v_min, int v { *v = new_value; value_changed = true; - ImGui::MarkItemEdited(id); // TODO only mark on mouse up } } @@ -365,14 +370,25 @@ void DrawSlippiPlaybackControls() // We have to provide a window name, and these shouldn't be duplicated. // So instead, we generate a name based on the number of messages drawn. const std::string window_name = fmt::format("Slippi Playback Controls"); - - const float alpha = 1.0f; - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha); - - ImGui::SetNextWindowPos(ImVec2(0, 0)); ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize); + bool isActive = false; + auto mousePos = ImGui::GetMousePos(); + auto mouseDown = GImGui->IO.MouseDown[0]; + if (mousePos[1] > ImGui::GetIO().DisplaySize.y - 44 && mousePos[1] < ImGui::GetIO().DisplaySize.y || mouseDown) { + isActive = true; + last_active_tick = std::chrono::steady_clock::now(); + } + + if (isActive) { + INFO_LOG(SLIPPI, "hovering!"); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.0f); + } + else { + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.00001f); + } + if (ImGui::Begin(window_name.c_str(), nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBackground |