diff --git a/src/JPEGView/FileList.cpp b/src/JPEGView/FileList.cpp index 79787a0f..46f9bda2 100644 --- a/src/JPEGView/FileList.cpp +++ b/src/JPEGView/FileList.cpp @@ -190,7 +190,7 @@ static LPCTSTR* GetSupportedFileEndingList() { CFileList::CFileList(const CString & sInitialFile, CDirectoryWatcher & directoryWatcher, Helpers::ESorting eInitialSorting, bool isSortedAscending, bool bWrapAroundFolder, int nLevel, bool forceSorting) : m_directoryWatcher(directoryWatcher) { - + m_isProcessing = false; CFileDesc::SetSorting(eInitialSorting, isSortedAscending); m_bDeleteHistory = true; m_bWrapAroundFolder = bWrapAroundFolder; @@ -217,9 +217,14 @@ CFileList::CFileList(const CString & sInitialFile, CDirectoryWatcher & directory if (!m_bIsSlideShowList) { if (bImageFile || bIsDirectory) { - FindFiles(); - m_iter = FindFile(sInitialFile); - m_iterStart = bWrapAroundFolder ? m_iter : m_fileList.begin(); + // Expensive operation, it will be run asynchronously to avoid blocking the main thread. + m_isProcessing = true; + m_future_fileList = std::async(std::launch::async, [this, &sInitialFile, &bWrapAroundFolder]() { + FindFiles(); + m_iter = FindFile(sInitialFile); + m_iterStart = bWrapAroundFolder ? m_iter : m_fileList.begin(); + m_isProcessing = false; + }); } else { // neither image file nor directory nor list of file names - try to read anyway but normally will fail CFindFile fileFind; @@ -256,6 +261,7 @@ CString CFileList::GetSupportedFileEndings() { void CFileList::Reload(LPCTSTR sFileName, bool clearForwardHistory) { LPCTSTR sCurrent = sFileName; if (sCurrent == NULL) { + WaitIfNotReady(); // If async is still processing, then wait. sCurrent = Current(); if (sCurrent == NULL) { m_fileList.clear(); @@ -339,6 +345,9 @@ void CFileList::FileHasRenamed(LPCTSTR sOldFileName, LPCTSTR sNewFileName) { m_sInitialFile = sNewFileName; } std::list::iterator iter; + + WaitIfNotReady(); // If async is still processing, then wait. + for (iter = m_fileList.begin( ); iter != m_fileList.end( ); iter++ ) { if (_tcsicmp(sOldFileName, iter->GetName()) == 0) { iter->SetName(sNewFileName); @@ -347,6 +356,8 @@ void CFileList::FileHasRenamed(LPCTSTR sOldFileName, LPCTSTR sNewFileName) { } void CFileList::ModificationTimeChanged() { + WaitIfNotReady(); // If async is still processing, then wait. + if (m_iter != m_fileList.end()) { LPCTSTR sName = m_iter->GetName(); HANDLE hFile = ::CreateFile(sName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); @@ -440,6 +451,8 @@ void CFileList::Last() { } CFileList* CFileList::AwayFromCurrent() { + WaitIfNotReady(); // If async is still processing, then wait. + LPCTSTR sCurrentFile = Current(); LPCTSTR sNextFile = PeekNextPrev(1, true, false); if (sCurrentFile == NULL || sNextFile == NULL || _tcscmp(sCurrentFile, sNextFile) == 0) { @@ -455,7 +468,10 @@ CFileList* CFileList::AwayFromCurrent() { } LPCTSTR CFileList::Current() const { - if (m_iter != m_fileList.end()) { + if (m_isProcessing) { // Most likely CFileList is being inialized, thus it's probably the initial file being requested. + return m_sInitialFile; + } + else if (m_iter != m_fileList.end()) { return m_iter->GetName(); } else { return NULL; @@ -505,6 +521,7 @@ LPCTSTR CFileList::PeekNextPrev(int nIndex, bool bForward, bool bToggle) { if (bToggle) { return (m_nMarkedIndexShow == 0) ? m_sMarkedFile : m_sMarkedFileCurrent; } else { + std::list::iterator thisIter = m_iter; LPCTSTR sFileName; if (nIndex != 0) { @@ -997,4 +1014,4 @@ bool CFileList::TryReadingSlideShowList(const CString & sSlideShowFile) { fclose(fptr); delete[] fileBuffOrig; return true; -} +} \ No newline at end of file diff --git a/src/JPEGView/FileList.h b/src/JPEGView/FileList.h index 50f8e6cf..7b9c3aa2 100644 --- a/src/JPEGView/FileList.h +++ b/src/JPEGView/FileList.h @@ -1,6 +1,7 @@ #pragma once #include "Helpers.h" +#include class CDirectoryWatcher; @@ -120,7 +121,10 @@ class CFileList void ToggleBetweenMarkedAndCurrentFile(); // Sets a checkpoint on the current image - void SetCheckpoint() { m_iterCheckPoint = m_iter; } + void SetCheckpoint() { + WaitIfNotReady(); + m_iterCheckPoint = m_iter; + } // Check if we are now on another image since the last checkpoint was set bool ChangedSinceCheckpoint() { return m_iterCheckPoint != m_iter; } @@ -145,6 +149,11 @@ class CFileList // delete the chain of CFileLists forward and backward and only leave the current node alive void DeleteHistory(bool onlyForward = false); + inline void CFileList::WaitIfNotReady() { + if (m_isProcessing) + m_future_fileList.wait(); + } + private: static Helpers::ENavigationMode sm_eMode; @@ -162,6 +171,10 @@ class CFileList std::list::iterator m_iterStart; // start of iteration in m_fileList std::list::iterator m_iterCheckPoint; + // Async + std::future m_future_fileList; + bool m_isProcessing; + CString m_sMarkedFile; CString m_sMarkedFileCurrent; int m_nMarkedIndexShow; diff --git a/src/JPEGView/JPEGProvider.cpp b/src/JPEGView/JPEGProvider.cpp index 7ea45801..6b9fecda 100644 --- a/src/JPEGView/JPEGProvider.cpp +++ b/src/JPEGView/JPEGProvider.cpp @@ -123,6 +123,81 @@ void CJPEGProvider::NotifyNotUsed(CJPEGImage* pImage) { if (pImage != NULL) delete pImage; } +// Only used for startup, to avoid accessing m_fileList. +CJPEGImage* CJPEGProvider::RequestImage(EReadAheadDirection eDirection, + LPCTSTR strFileName, int nFrameIndex, const CProcessParams& processParams, + bool& bOutOfMemory, bool& bExceptionError) { + if (strFileName == NULL) { + bOutOfMemory = false; + bExceptionError = false; + return NULL; + } + + // Search if we have the requested image already present or in progress + CImageRequest* pRequest = FindRequest(strFileName, nFrameIndex); + bool bDirectionChanged = eDirection != m_eOldDirection || eDirection == TOGGLE; + bool bRemoveAlsoActiveRequests = bDirectionChanged; // if direction changed, all read-ahead requests are wrongly guessed + bool bWasOutOfMemory = false; + m_eOldDirection = eDirection; + + if (pRequest == NULL) { + // no request pending for this file, add to request queue and start async + pRequest = StartNewRequest(strFileName, nFrameIndex, processParams); + } + + // wait for request if not yet ready + if (!pRequest->Ready) { +#ifdef DEBUG + ::OutputDebugString(_T("Waiting for request: ")); ::OutputDebugString(pRequest->FileName); ::OutputDebugString(_T("\n")); +#endif + ::WaitForSingleObject(pRequest->EventFinished, INFINITE); + GetLoadedImageFromWorkThread(pRequest); + } + else { + CJPEGImage* pImage = pRequest->Image; + if (pImage != NULL) { + // make sure the initial parameters are reset as when keep params was on before they are wrong + EProcessingFlags procFlags = processParams.ProcFlags; + pImage->RestoreInitialParameters(strFileName, processParams.ImageProcParams, procFlags, + processParams.RotationParams.Rotation, processParams.Zoom, processParams.Offsets, + CSize(processParams.TargetWidth, processParams.TargetHeight), processParams.MonitorSize); + } +#ifdef DEBUG + ::OutputDebugString(_T("Found in cache: ")); ::OutputDebugString(pRequest->FileName); ::OutputDebugString(_T("\n")); +#endif + } + + // set before removing unused images! + pRequest->InUse = true; + pRequest->AccessTimeStamp = m_nCurrentTimeStamp++; + + if (pRequest->OutOfMemory) { + // The request could not be satisfied because the system is out of memory. + // Clear all memory and try again - maybe some readahead requests can be deleted +#ifdef DEBUG + ::OutputDebugString(_T("Retrying request because out of memory: ")); ::OutputDebugString(pRequest->FileName); ::OutputDebugString(_T("\n")); +#endif + bWasOutOfMemory = true; + if (FreeAllPossibleMemory()) { + DeleteElement(pRequest); + pRequest = StartRequestAndWaitUntilReady(strFileName, nFrameIndex, processParams); + } + } + + // cleanup stuff no longer used + RemoveUnusedImages(bRemoveAlsoActiveRequests); + ClearOldestInactiveRequest(); + + // check if we shall start new requests (don't start another request if we are short of memory!) + //if (m_requestList.size() < (unsigned int)m_nNumBuffers && !bDirectionChanged && !bWasOutOfMemory && eDirection != NONE) { + // StartNewRequestBundle(pFileList, eDirection, processParams, m_nNumThread, pRequest); + //} + + bOutOfMemory = pRequest->OutOfMemory; + bExceptionError = pRequest->ExceptionError; + return pRequest->Image; +} + void CJPEGProvider::ClearAllRequests() { std::list::iterator iter; for (iter = m_requestList.begin( ); iter != m_requestList.end( ); iter++ ) { diff --git a/src/JPEGView/JPEGProvider.h b/src/JPEGView/JPEGProvider.h index 23f4d1cd..e8436476 100644 --- a/src/JPEGView/JPEGProvider.h +++ b/src/JPEGView/JPEGProvider.h @@ -41,6 +41,9 @@ class CJPEGProvider // created automatically so that the next image will be ready immediately when requested in the future. CJPEGImage* RequestImage(CFileList* pFileList, EReadAheadDirection eDirection, LPCTSTR strFileName, int nFrameIndex, const CProcessParams & processParams, bool& bOutOfMemory, bool& bExceptionError); + CJPEGImage* CJPEGProvider::RequestImage(EReadAheadDirection eDirection, + LPCTSTR strFileName, int nFrameIndex, const CProcessParams& processParams, + bool& bOutOfMemory, bool& bExceptionError); // Notifies that the specified image is no longer used and its memory can be freed. // The CJPEGProvider class may decide to keep the image cached. diff --git a/src/JPEGView/MainDlg.cpp b/src/JPEGView/MainDlg.cpp index c16bc951..75bb0bac 100644 --- a/src/JPEGView/MainDlg.cpp +++ b/src/JPEGView/MainDlg.cpp @@ -380,8 +380,8 @@ LRESULT CMainDlg::OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam CProcessingThreadPool::This().CreateThreadPoolThreads(); // create JPEG provider and request first image - do no processing yet if not in fullscreen mode (as we do not know the size yet) - m_pJPEGProvider = new CJPEGProvider(m_hWnd, NUM_THREADS, READ_AHEAD_BUFFERS); - m_pCurrentImage = m_pJPEGProvider->RequestImage(m_pFileList, CJPEGProvider::FORWARD, + m_pJPEGProvider = new CJPEGProvider(m_hWnd, NUM_THREADS, READ_AHEAD_BUFFERS); + m_pCurrentImage = m_pJPEGProvider->RequestImage(CJPEGProvider::FORWARD, m_pFileList->Current(), 0, CreateProcessParams(!m_bFullScreenMode), m_bOutOfMemoryLastImage, m_bExceptionErrorLastImage); if (m_pCurrentImage != NULL && m_pCurrentImage->IsAnimation()) { StartAnimation(); @@ -2144,11 +2144,12 @@ void CMainDlg::OpenFile(LPCTSTR sFileName, bool bAfterStartup) { delete m_pFileList; m_sStartupFile = sFileName; m_pFileList = new CFileList(m_sStartupFile, *m_pDirectoryWatcher, eOldSorting, oOldAscending, CSettingsProvider::This().WrapAroundFolder()); + // free current image and all read ahead images InitParametersForNewImage(); m_pJPEGProvider->NotifyNotUsed(m_pCurrentImage); m_pJPEGProvider->ClearAllRequests(); - m_pCurrentImage = m_pJPEGProvider->RequestImage(m_pFileList, CJPEGProvider::FORWARD, + m_pCurrentImage = m_pJPEGProvider->RequestImage(CJPEGProvider::FORWARD, m_pFileList->Current(), 0, CreateProcessParams(!m_bFullScreenMode && (bAfterStartup || IsAdjustWindowToImage())), m_bOutOfMemoryLastImage, m_bExceptionErrorLastImage); m_nLastLoadError = GetLoadErrorAfterOpenFile();