You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/CryEngine/CrySystem/StreamEngine/StreamIOThread.cpp

820 lines
25 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
// Description : Streaming Thread for IO
#include "CrySystem_precompiled.h"
#include "StreamIOThread.h"
#include "StreamEngine.h"
#include "../System.h"
extern SSystemCVars g_cvars;
//#pragma("control %push O=0") // to disable optimization
//////////////////////////////////////////////////////////////////////////
CStreamingIOThread::CStreamingIOThread(CStreamEngine* pStreamEngine, EStreamSourceMediaType mediaType, const char* name)
{
m_pStreamEngine = pStreamEngine;
m_bCancelThreadRequest = false;
m_bNeedSorting = false;
m_bNeedReset = false;
m_bNewRequests = false;
m_name = name;
m_eMediaType = mediaType;
m_nFallbackMTs = 0;
m_iUrgentRequests = 0;
m_bPaused = false;
m_bAbortReads = false;
m_nReadCounter = 0;
m_nStreamingCPU = -1;
Start((unsigned)(1 << g_cvars.sys_streaming_cpu), name);
}
CStreamingIOThread::~CStreamingIOThread()
{
Cancel();
Stop();
WaitForThread();
}
//////////////////////////////////////////////////////////////////////////
void CStreamingIOThread::AddRequest(CAsyncIOFileRequest* pRequest, bool bStartImmidietly)
{
pRequest->AddRef(); // Acquire ownership on file request.
pRequest->m_status = CAsyncIOFileRequest::eStatusInFileQueue;
if (pRequest->m_eMediaType != eStreamSourceTypeMemory)
{
pRequest->m_eMediaType = m_eMediaType;
}
// does this ignore the tmp out of memory
if (pRequest->IgnoreOutofTmpMem())
{
CryInterlockedIncrement(&m_iUrgentRequests);
}
m_newFileRequests.push_back(pRequest);
if (bStartImmidietly)
{
READ_WRITE_BARRIER
m_bNewRequests = true;
m_awakeEvent.Set();
}
}
//////////////////////////////////////////////////////////////////////////
void CStreamingIOThread::SignalStartWork(bool bForce)
{
if (!m_newFileRequests.empty() || bForce)
{
READ_WRITE_BARRIER
m_bNewRequests = true;
m_awakeEvent.Set();
}
}
//////////////////////////////////////////////////////////////////////////
void CStreamingIOThread::Pause(bool bPause)
{
m_bPaused = bPause;
}
//////////////////////////////////////////////////////////////////////////
void CStreamingIOThread::Run()
{
SetName(m_name);
CTimeValue t0 = gEnv->pTimer->GetAsyncTime();
m_nLastReadDiskOffset = 0;
//
// Main thread loop
while (!m_bCancelThreadRequest)
{
if (m_nStreamingCPU != g_cvars.sys_streaming_cpu)
{
m_nStreamingCPU = g_cvars.sys_streaming_cpu;
#if defined(AZ_RESTRICTED_PLATFORM)
#undef AZ_RESTRICTED_SECTION
#define STREAMIOTHREAD_CPP_SECTION_1 1
#define STREAMIOTHREAD_CPP_SECTION_2 2
#endif
#if defined(AZ_RESTRICTED_PLATFORM)
#define AZ_RESTRICTED_SECTION STREAMIOTHREAD_CPP_SECTION_1
#include AZ_RESTRICTED_FILE(StreamEngine/StreamIOThread_cpp)
#endif
}
if (m_bNewRequests || !m_newFileRequests.empty())
{
READ_WRITE_BARRIER
ProcessNewRequests();
}
else
{
#if defined(_RELEASE)
m_awakeEvent.Wait();
#elif defined(STREAMENGINE_ENABLE_STATS)
// compute max time to wait - revive thread every second at least once to update stats
bool bWaiting = true;
while (bWaiting)
{
CTimeValue t1 = gEnv->pTimer->GetAsyncTime();
CTimeValue deltaT = t1 - t0;
uint64 msec = deltaT.GetMilliSecondsAsInt64();
if (msec < 1000)
{
bWaiting = !m_awakeEvent.Wait(1000 - (uint32)msec);
}
if (bWaiting)
{
// update the delta time again
t1 = gEnv->pTimer->GetAsyncTime();
deltaT = t1 - t0;
m_InMemoryStats.Update(deltaT);
m_NotInMemoryStats.Update(deltaT);
t0 = t1;
}
}
#endif
}
if (m_bNeedReset)
{
ProcessReset();
}
bool bIsOOM = false;
while (!m_bCancelThreadRequest && !m_fileRequestQueue.empty())
{
CAsyncIOFileRequest_TransferPtr pFileRequest(m_fileRequestQueue.back());
m_fileRequestQueue.pop_back();
assert (&*pFileRequest);
if (pFileRequest->HasFailed())
{
// check if request was high prio, then decr open count
if (pFileRequest->IgnoreOutofTmpMem())
{
CryInterlockedDecrement(&m_iUrgentRequests);
}
CAsyncIOFileRequest::JobFinalize_Read(pFileRequest, m_pStreamEngine->GetJobEngineState());
continue;
}
//////////////////////////////////////////////////////////////////////////
// When temporary memory goes out of budget we must loop here and wait until previous file requests are finished and free up memory.
// Only allow processing of requests which are flagged for processing when out of tmp memory
//////////////////////////////////////////////////////////////////////////
if (bIsOOM && !m_bCancelThreadRequest)
{
m_pStreamEngine->FlagTempMemOutOfBudget();
if (m_iUrgentRequests > 0)
{
if (m_bNewRequests || !m_newFileRequests.empty())
{
READ_WRITE_BARRIER
ProcessNewRequests();
}
// readd the current request
m_fileRequestQueue.push_back(pFileRequest.Relinquish());
// search for the first request which ignores the current out of mem state
// Search for next highest priority request
std::vector<CAsyncIOFileRequest*>::reverse_iterator rit;
for (rit = m_fileRequestQueue.rbegin(); rit != m_fileRequestQueue.rend(); ++rit)
{
if ((*rit)->IgnoreOutofTmpMem())
{
pFileRequest = *rit;
std::vector<CAsyncIOFileRequest*>::iterator it(rit.base());
--it;
m_fileRequestQueue.erase(it);
break;
}
}
}
else
{
// read the current request
m_fileRequestQueue.push_back(pFileRequest.Relinquish());
}
}
// Simply let the io thread sleep when paused before doing any actual IO
while (m_bPaused)
{
CrySleep(10);
}
// If at this point, the filerequest is zero, the above prioritization of
// urgent requests couldn't find a new task to displace the current
// one. As the current one had been pushed back previously, we can safely
// assume that restarting the loop will grab it again (eventually).
if (!pFileRequest)
{
break;
}
// check if request was high prio, then decr open count
if (pFileRequest->IgnoreOutofTmpMem())
{
CryInterlockedDecrement(&m_iUrgentRequests);
}
bIsOOM = false;
uint32 nSizeOnMedia = pFileRequest->m_nSizeOnMedia;
uint32 nError = 0;
// Handle file request.
if (m_bAbortReads)
{
nError = ERROR_ABORTED_ON_SHUTDOWN;
}
else if (pFileRequest->m_bReadBegun)
{
nError = pFileRequest->ReadFileResume(this);
}
else
{
nError = pFileRequest->ReadFile(this);
}
#ifdef STREAMENGINE_ENABLE_STATS
pFileRequest->m_nReadCounter = m_nReadCounter++;
#endif
if (nError == 0)
{
if (pFileRequest->m_eMediaType != eStreamSourceTypeMemory)
{
pFileRequest->m_nReadHeadOffsetKB = (int32)(((int64)pFileRequest->m_nDiskOffset - m_nLastReadDiskOffset) >> 10); // in KB
m_nLastReadDiskOffset = pFileRequest->m_nDiskOffset + nSizeOnMedia;
#ifdef STREAMENGINE_ENABLE_STATS
m_NotInMemoryStats.m_nTempReadOffset += abs(pFileRequest->m_nReadHeadOffsetKB);
m_NotInMemoryStats.m_nTotalReadOffset += abs(pFileRequest->m_nReadHeadOffsetKB);
m_NotInMemoryStats.m_nTempRequestCount++;
// Calc IO bandwidth only for non memory files.
m_NotInMemoryStats.m_nTempBytesRead += nSizeOnMedia;
m_NotInMemoryStats.m_TempReadTime += pFileRequest->m_readTime;
#endif
}
else
{
#ifdef STREAMENGINE_ENABLE_STATS
m_InMemoryStats.m_nTempRequestCount++;
// Calc IO bandwidth only for in memory files.
m_InMemoryStats.m_nTempBytesRead += nSizeOnMedia;
m_InMemoryStats.m_TempReadTime += pFileRequest->m_readTime;
#endif
}
CAsyncIOFileRequest::JobFinalize_Read(pFileRequest, m_pStreamEngine->GetJobEngineState());
}
else
{
switch (nError)
{
case ERROR_OUT_OF_MEMORY:
bIsOOM = true;
pFileRequest->SetPriority(estpPreempted);
if (pFileRequest->IgnoreOutofTmpMem())
{
CryInterlockedIncrement(&m_iUrgentRequests);
}
m_fileRequestQueue.push_back(pFileRequest.Relinquish());
m_bNewRequests = true;
break;
case ERROR_PREEMPTED:
pFileRequest->SetPriority(estpPreempted);
if (pFileRequest->IgnoreOutofTmpMem())
{
CryInterlockedIncrement(&m_iUrgentRequests);
}
m_fileRequestQueue.push_back(pFileRequest.Relinquish());
m_bNewRequests = true;
break;
case ERROR_MISSCHEDULED:
// Request tried to read a file that has changed media type. Reset the sort key
// and reschedule.
pFileRequest->m_bSortKeyComputed = 0;
AddRequest(&*pFileRequest, false);
break;
default:
pFileRequest->SyncWithDecompress();
pFileRequest->Failed(nError);
CAsyncIOFileRequest::JobFinalize_Read(pFileRequest, m_pStreamEngine->GetJobEngineState());
break;
}
}
//////////////////////////////////////////////////////////////////////////
if (m_bNewRequests)
{
READ_WRITE_BARRIER
ProcessNewRequests();
}
if (m_bNeedReset)
{
ProcessReset();
}
if (m_bNeedSorting)
{
SortRequests();
}
//////////////////////////////////////////////////////////////////////////
#ifdef STREAMENGINE_ENABLE_STATS
if (g_cvars.sys_streaming_max_bandwidth != 0)
{
CTimeValue t1 = gEnv->pTimer->GetAsyncTime();
CTimeValue deltaT = t1 - t0;
// Sleep in case we are streaming too fast.
const float fTheoreticalReadTime = float(nSizeOnMedia) / g_cvars.sys_streaming_max_bandwidth * 0.00000095367431640625f; // / (1024*1024)
if (fTheoreticalReadTime - deltaT.GetSeconds() > FLT_EPSILON)
{
uint32 nSleepTime = uint32(1000.f * (fTheoreticalReadTime - deltaT.GetSeconds()));
CrySleep(nSleepTime);
}
}
CTimeValue t1 = gEnv->pTimer->GetAsyncTime();
CTimeValue deltaT = t1 - t0;
// update the stats every second
if (deltaT.GetMilliSecondsAsInt64() > 1000)
{
m_InMemoryStats.Update(deltaT);
m_NotInMemoryStats.Update(deltaT);
t0 = t1;
}
#endif
}
}
}
#ifdef STREAMENGINE_ENABLE_STATS
void CStreamingIOThread::SStats::Update(const CTimeValue& deltaT)
{
m_nReadBytesInLastSecond = (uint32)m_nTempBytesRead;
m_nRequestCountInLastSecond = m_nTempRequestCount;
m_nTotalReadBytes += (uint32)m_nTempBytesRead;
m_nTotalRequestCount += m_nTempRequestCount;
m_TotalReadTime += m_TempReadTime;
if (m_TempReadTime.GetValue() != 0)
{
m_nActualReadBandwith = (uint32)(m_nTempBytesRead / m_TempReadTime.GetSeconds());
}
else
{
m_nActualReadBandwith = 0;
}
m_nCurrentReadBandwith = (uint32)(m_nTempBytesRead / deltaT.GetSeconds());
m_fReadingDuringLastSecond = m_TempReadTime.GetSeconds() / deltaT.GetSeconds() * 100;
if (m_nTempRequestCount > 0)
{
m_nReadOffsetInLastSecond = m_nTempReadOffset / m_nTempRequestCount;
}
else
{
m_nReadOffsetInLastSecond = 0;
}
m_TempReadTime.SetValue(0);
m_nTempBytesRead = 0;
m_nTempReadOffset = 0;
m_nTempRequestCount = 0;
}
#endif
//////////////////////////////////////////////////////////////////////////
void CStreamingIOThread::Cancel()
{
m_bCancelThreadRequest = true;
m_awakeEvent.Set();
}
//////////////////////////////////////////////////////////////////////////
struct SCompareAsyncFileRequest
{
bool operator()(CAsyncIOFileRequest* pFile1, CAsyncIOFileRequest* pFile2) const
{
return pFile1->m_nSortKey > pFile2->m_nSortKey;
}
};
//////////////////////////////////////////////////////////////////////////
void CStreamingIOThread::SortRequests()
{
FUNCTION_PROFILER(gEnv->pSystem, PROFILE_SYSTEM);
std::sort(m_fileRequestQueue.begin(), m_fileRequestQueue.end(), SCompareAsyncFileRequest());
/*
int nStartOfQueue = 0;
int64 nDiskOffsetLimit = m_nLastReadDiskOffset - 32*1024; // 32KB less only
int nCount = (int)m_fileRequestQueue.size();
for (int i = nCount-1; i >= 0; i--)
{
if (m_fileRequestQueue[i]->m_nDiskOffset > nDiskOffsetLimit)
{
nStartOfQueue = i+1;
break;
}
}
if (nStartOfQueue < nCount && nStartOfQueue > 0)
{
int nElements = nCount - nStartOfQueue;
// Move all elements up to nStartOfQueue, from begining of the request array to the end.
m_temporaryArray.resize(0);
// Copy to temp array elements up to nStartOfQueue
m_temporaryArray.insert( m_temporaryArray.end(),m_fileRequestQueue.begin()+nStartOfQueue,m_fileRequestQueue.end() );
// Remove elements up to nStartOfQueue from request list
m_fileRequestQueue.erase( m_fileRequestQueue.begin()+nStartOfQueue,m_fileRequestQueue.end() );
// Add elemenets at the end from temp array.
m_fileRequestQueue.insert( m_fileRequestQueue.begin(),m_temporaryArray.begin(),m_temporaryArray.end() );
}
*/
m_bNeedSorting = false;
}
void CStreamingIOThread::NeedSorting()
{
m_bNeedSorting = true;
}
//////////////////////////////////////////////////////////////////////////
void CStreamingIOThread::ProcessNewRequests()
{
m_bNewRequests = false;
std::vector<CAsyncIOFileRequest*> temporaryArray;
temporaryArray.reserve(m_newFileRequests.size());
m_newFileRequests.swap(temporaryArray);
std::vector<CAsyncIOFileRequest*>& newFiles = temporaryArray;
if (!newFiles.empty())
{
uint64 nCurrentKeyInProgress = m_fileRequestQueue.size() ? m_fileRequestQueue.back()->m_nSortKey : 0;
// Compute sorting key for new file entries.
int iWakeFallback(0);
const TFallbackIOVecConstIt itEnd = m_FallbackIOThreads.end();
const size_t fallbackNum = m_FallbackIOThreads.size();
PREFAST_SUPPRESS_WARNING(6255)
uint8 * pFallbackSignals = fallbackNum ? (uint8*)alloca(fallbackNum) : NULL;
for (uint32 fb = 0; fb < fallbackNum; ++fb)
{
pFallbackSignals[fb] = 0;
}
for (size_t i = 0, num = newFiles.size(); i < num; i++)
{
CAsyncIOFileRequest* pFilepRequest = newFiles[i];
pFilepRequest->ComputeSortKey(nCurrentKeyInProgress);
static_cast<CReadStream*>(&*pFilepRequest->m_pReadStream)->ComputedMediaType(pFilepRequest->m_eMediaType);
#ifdef STREAMENGINE_ENABLE_LISTENER
IStreamEngineListener* pListener = m_pStreamEngine->GetListener();
if (pListener)
{
pListener->OnStreamComputedSortKey(pFilepRequest, pFilepRequest->m_nSortKey);
}
#endif
bool bFallback = false;
int idx = -1;
for (TFallbackIOVecConstIt it = m_FallbackIOThreads.begin(); it != itEnd && !bFallback; ++it)
{
++idx;
if (it->second == pFilepRequest->GetMediaType())
{
if (pFilepRequest->IgnoreOutofTmpMem())
{
CryInterlockedDecrement(&m_iUrgentRequests);
}
(it->first)->AddRequest(pFilepRequest, true);
pFilepRequest->Release(); // Release local ownership of request (moved to fallback IO thread)
iWakeFallback++;
bFallback = true;
pFallbackSignals[idx] = 1;
}
}
if (!bFallback)
{
m_fileRequestQueue.push_back(pFilepRequest);
}
}
for (uint32 fb = 0; fb < fallbackNum; ++fb)
{
if (pFallbackSignals[fb] != 0)
{
(m_FallbackIOThreads[fb].first)->SignalStartWork(false);
}
}
SortRequests();
/*
if (m_fileRequestQueue.back() != pRequest && pRequest != 0)
{
// Highest priority changed.
if (m_fileRequestQueue.back()->m_nDiskOffset < (m_nLastReadDiskOffset-32*1024))
{
//CryLog( "Bad Offset in Queue" );
}
}
*/
}
}
void CStreamingIOThread::ProcessReset()
{
if (!m_fileRequestQueue.empty())
{
for (std::vector<CAsyncIOFileRequest*>::iterator it = m_fileRequestQueue.begin(), itEnd = m_fileRequestQueue.end(); it != itEnd; ++it)
{
(*it)->Release();
}
}
stl::free_container(m_fileRequestQueue);
if (!m_temporaryArray.empty())
{
for (std::vector<CAsyncIOFileRequest*>::iterator it = m_temporaryArray.begin(), itEnd = m_temporaryArray.end(); it != itEnd; ++it)
{
(*it)->Release();
}
}
stl::free_container(m_temporaryArray);
m_bNeedReset = false;
m_resetDoneEvent.Set();
}
//////////////////////////////////////////////////////////////////////////
void CStreamingIOThread::CancelAll()
{
{
CryMT::vector<CAsyncIOFileRequest*>::AutoLock lock(m_newFileRequests.get_lock());
if (!m_newFileRequests.empty())
{
CAsyncIOFileRequest* const* it = &m_newFileRequests.front();
CAsyncIOFileRequest* const* itEnd = it + m_newFileRequests.size();
for (; it != itEnd; ++it)
{
(*it)->Release();
}
}
}
m_newFileRequests.free_memory();
m_iUrgentRequests = 0;
}
void CStreamingIOThread::AbortAll(bool bAbort)
{
m_bAbortReads = bAbort;
}
void CStreamingIOThread::BeginReset()
{
CancelAll();
m_resetDoneEvent.Reset();
m_bNeedReset = true;
m_awakeEvent.Set();
}
void CStreamingIOThread::EndReset()
{
m_resetDoneEvent.Wait();
}
//////////////////////////////////////////////////////////////////////////
void CStreamingIOThread::RegisterFallbackIOThread(EStreamSourceMediaType mediaType, CStreamingIOThread* pIOThread)
{
//check if media has not yet been registered
if (!pIOThread)
{
return;//no need for NULL register anymore
}
const TFallbackIOVecConstIt itEnd = m_FallbackIOThreads.end();
for (TFallbackIOVecConstIt it = m_FallbackIOThreads.begin(); it != itEnd; ++it)
{
if (it->second == mediaType)
{
return;
}
}
m_FallbackIOThreads.push_back(std::make_pair(pIOThread, mediaType));
m_nFallbackMTs |= 1 << mediaType;
}
bool CStreamingIOThread::HasUrgentRequests()
{
bool ret = false;
if (m_iUrgentRequests > 0)
{
//lock to prevent list modification whilst traversing
m_newFileRequests.get_lock().Lock();
int nRequests = m_newFileRequests.size();
if (nRequests)
{
for (int i = 0; i < nRequests; i++)
{
if (m_newFileRequests[i]->m_ePriority == estpUrgent)
{
//printf("Urgent task pending: %s\n", m_newFileRequests[i]->m_strFileName.c_str());
ret = true;
break;
}
}
}
m_newFileRequests.get_lock().Unlock();
}
return ret;
}
bool CStreamingIOThread::IsMisscheduled(EStreamSourceMediaType mt) const
{
if (mt == m_eMediaType)
{
return false;
}
if (m_nFallbackMTs & (1 << mt))
{
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
CStreamingWorkerThread::CStreamingWorkerThread(CStreamEngine* pStreamEngine, const char* name, EWorkerType type, SStreamRequestQueue* pQueue)
{
m_type = type;
m_name = name;
m_pStreamEngine = pStreamEngine;
m_pQueue = pQueue;
m_bCancelThreadRequest = false;
m_bNeedsReset = false;
Start((unsigned)1 << g_cvars.sys_streaming_cpu_worker, name);
}
CStreamingWorkerThread::~CStreamingWorkerThread()
{
Cancel();
Stop();
WaitForThread();
}
//////////////////////////////////////////////////////////////////////////
void CStreamingWorkerThread::Run()
{
SetName(m_name);
#if defined(AZ_RESTRICTED_PLATFORM)
#define AZ_RESTRICTED_SECTION STREAMIOTHREAD_CPP_SECTION_2
#include AZ_RESTRICTED_FILE(StreamEngine/StreamIOThread_cpp)
#endif
// Main thread loop
while (!m_bCancelThreadRequest)
{
m_pQueue->m_awakeEvent.Wait();
m_pQueue->m_awakeEvent.Reset();
CAsyncIOFileRequest_AutoPtr pFileRequest;
while (!m_bCancelThreadRequest && !m_bNeedsReset && m_pQueue->TryPopRequest(pFileRequest))
{
switch (m_type)
{
case eWorkerAsyncCallback:
{
#ifndef _RELEASE
float fTime = gEnv->pTimer->GetAsyncCurTime();
#endif
m_pStreamEngine->ReportAsyncFileRequestComplete(pFileRequest);
#ifndef _RELEASE
float fTime1 = gEnv->pTimer->GetAsyncCurTime();
#endif
#ifdef STREAMENGINE_ENABLE_STATS
CryInterlockedDecrement(&m_pStreamEngine->GetStreamingStatistics().nCurrentAsyncCount);
#endif
#ifndef _RELEASE
if ((fTime1 - fTime) > 1.f && !pFileRequest->m_strFileName.empty())
{
string str;
str.Format("[ACALL] %s time=%.5f\n", pFileRequest->m_strFileName.c_str(), (fTime1 - fTime));
if (gEnv && gEnv->pSystem && gEnv->pLog)
{
gEnv->pLog->Log(str.c_str());
}
}
#endif
}
break;
}
}
if (m_bNeedsReset)
{
m_pQueue->Reset();
m_bNeedsReset = false;
m_resetDoneEvent.Set();
}
}
}
//////////////////////////////////////////////////////////////////////////
void CStreamingWorkerThread::Cancel()
{
m_bCancelThreadRequest = true;
m_pQueue->m_awakeEvent.Set();
}
//////////////////////////////////////////////////////////////////////////
void CStreamingWorkerThread::CancelAll()
{
m_pQueue->Reset();
}
void CStreamingWorkerThread::BeginReset()
{
CancelAll();
m_resetDoneEvent.Reset();
m_bNeedsReset = true;
m_pQueue->m_awakeEvent.Set();
}
void CStreamingWorkerThread::EndReset()
{
m_resetDoneEvent.Wait();
}