Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Asynchronous FileList #172

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
29 changes: 23 additions & 6 deletions src/JPEGView/FileList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -339,6 +345,9 @@ void CFileList::FileHasRenamed(LPCTSTR sOldFileName, LPCTSTR sNewFileName) {
m_sInitialFile = sNewFileName;
}
std::list<CFileDesc>::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);
Expand All @@ -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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand Down Expand Up @@ -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<CFileDesc>::iterator thisIter = m_iter;
LPCTSTR sFileName;
if (nIndex != 0) {
Expand Down Expand Up @@ -997,4 +1014,4 @@ bool CFileList::TryReadingSlideShowList(const CString & sSlideShowFile) {
fclose(fptr);
delete[] fileBuffOrig;
return true;
}
}
15 changes: 14 additions & 1 deletion src/JPEGView/FileList.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include "Helpers.h"
#include <future>

class CDirectoryWatcher;

Expand Down Expand Up @@ -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; }

Expand All @@ -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;

Expand All @@ -162,6 +171,10 @@ class CFileList
std::list<CFileDesc>::iterator m_iterStart; // start of iteration in m_fileList
std::list<CFileDesc>::iterator m_iterCheckPoint;

// Async
std::future<void> m_future_fileList;
bool m_isProcessing;

CString m_sMarkedFile;
CString m_sMarkedFileCurrent;
int m_nMarkedIndexShow;
Expand Down
75 changes: 75 additions & 0 deletions src/JPEGView/JPEGProvider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<CImageRequest*>::iterator iter;
for (iter = m_requestList.begin( ); iter != m_requestList.end( ); iter++ ) {
Expand Down
3 changes: 3 additions & 0 deletions src/JPEGView/JPEGProvider.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 4 additions & 3 deletions src/JPEGView/MainDlg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down