|
|
|
|
//====== Copyright <EFBFBD>, Valve Corporation, All rights reserved. =======
|
|
|
|
|
// GCSqlWriteQueue.h
|
|
|
|
|
//
|
|
|
|
|
// A utility class that allows for templating based upon a SQL schema type, and then
|
|
|
|
|
// queuing up a number of those records to be written to SQL. This will buffer them until
|
|
|
|
|
// either a certain number have been queued or a period of time has elapsed, at which point
|
|
|
|
|
// it will flush them to SQL in a single transaction.
|
|
|
|
|
//
|
|
|
|
|
//===================================================================
|
|
|
|
|
#ifndef GCSQLWRITEQUEUE_H
|
|
|
|
|
#define GCSQLWRITEQUEUE_H
|
|
|
|
|
|
|
|
|
|
#include "scheduledfunction.h"
|
|
|
|
|
|
|
|
|
|
namespace GCSDK
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
class CGCBase;
|
|
|
|
|
|
|
|
|
|
template < typename TSqlClass >
|
|
|
|
|
class CGCSQLWriteQueue
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
|
|
|
|
|
CGCSQLWriteQueue( uint32 nMaxToCache, uint32 nMaxMSToWrite ) :
|
|
|
|
|
m_nMaxToCache( nMaxToCache ),
|
|
|
|
|
m_nMaxMSToWrite( nMaxMSToWrite )
|
|
|
|
|
{
|
|
|
|
|
m_QueuedRecords.EnsureCapacity( nMaxToCache );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//called to queue the record for writing, which will occur either when the maximum time between writebacks has occurred, or
|
|
|
|
|
//when the cache has filled up
|
|
|
|
|
void QueueRecord( const TSqlClass& sch )
|
|
|
|
|
{
|
|
|
|
|
m_QueuedRecords.AddToTail( sch );
|
|
|
|
|
|
|
|
|
|
//now handle either dispatching, or scheduling a timeout dispatch
|
|
|
|
|
if( ( uint32 )m_QueuedRecords.Count() >= m_nMaxToCache )
|
|
|
|
|
{
|
|
|
|
|
//unschedule first. This way if while we are yielded, we add another entry, it can schedule a new timeout
|
|
|
|
|
m_TimeCommit.Cancel();
|
|
|
|
|
CreateJobToCommitSQL();
|
|
|
|
|
}
|
|
|
|
|
else if( !m_TimeCommit.BIsScheduled() )
|
|
|
|
|
{
|
|
|
|
|
m_TimeCommit.Schedule( this, &CGCSQLWriteQueue< TSqlClass >::CreateJobToCommitSQL, m_nMaxMSToWrite );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//a yielding version of the above function
|
|
|
|
|
void YieldingQueueRecord( const TSqlClass& sch )
|
|
|
|
|
{
|
|
|
|
|
m_QueuedRecords.AddToTail( sch );
|
|
|
|
|
|
|
|
|
|
//now handle either dispatching, or scheduling a timeout dispatch
|
|
|
|
|
if( ( uint32 )m_QueuedRecords.Count() >= m_nMaxToCache )
|
|
|
|
|
{
|
|
|
|
|
//unschedule first. This way if while we are yielded, we add another entry, it can schedule a new timeout
|
|
|
|
|
m_TimeCommit.Cancel();
|
|
|
|
|
YieldingFlushQueuedViewsToSQL();
|
|
|
|
|
}
|
|
|
|
|
else if( !m_TimeCommit.BIsScheduled() )
|
|
|
|
|
{
|
|
|
|
|
m_TimeCommit.Schedule( this, &CGCSQLWriteQueue< TSqlClass >::CreateJobToCommitSQL, m_nMaxMSToWrite );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
|
|
|
|
//called internally when we kick off a job after N amount of time has expired
|
|
|
|
|
template < typename TSqlClass >
|
|
|
|
|
class CTimeExpiredCommitJob : public CGCJob
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
CTimeExpiredCommitJob( CGCBase* pGC, CGCSQLWriteQueue< TSqlClass >* pClass ) : CGCJob( pGC ), m_pClass( pClass ) {}
|
|
|
|
|
virtual bool BYieldingRunJob( void* pvStartParm )
|
|
|
|
|
{
|
|
|
|
|
m_pClass->YieldingFlushQueuedViewsToSQL();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
private:
|
|
|
|
|
CGCSQLWriteQueue< TSqlClass >* m_pClass;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//the function called when time expires to start a job and commit the requests to SQL
|
|
|
|
|
void CreateJobToCommitSQL()
|
|
|
|
|
{
|
|
|
|
|
//kick off our job, which just calls the flush
|
|
|
|
|
CGCJob* pJob = new CTimeExpiredCommitJob< TSqlClass >( GGCBase(), this );
|
|
|
|
|
pJob->StartJobDelayed( NULL );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//handles committing the list of queued views to SQL
|
|
|
|
|
void YieldingFlushQueuedViewsToSQL()
|
|
|
|
|
{
|
|
|
|
|
if( m_QueuedRecords.Count() == 0 )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
//move the contents into a local vector so we don't have any conflicts of global state
|
|
|
|
|
CUtlVector< TSqlClass > localQueue;
|
|
|
|
|
localQueue.Swap( m_QueuedRecords );
|
|
|
|
|
//prepare the queue for the next batch (so we don't have intermediate resizes)
|
|
|
|
|
m_QueuedRecords.EnsureCapacity( m_nMaxToCache );
|
|
|
|
|
|
|
|
|
|
// start a transaction for all this work
|
|
|
|
|
CSQLAccess sqlAccess;
|
|
|
|
|
sqlAccess.BBeginTransaction( "CGCWatchDownloadedReplayJob::FlushQueuedViewsToSQL" );
|
|
|
|
|
|
|
|
|
|
FOR_EACH_VEC( localQueue, nCurrView )
|
|
|
|
|
{
|
|
|
|
|
sqlAccess.BYieldingInsertRecord( &localQueue[ nCurrView ] );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sqlAccess.BCommitTransaction();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//the records that we have queued
|
|
|
|
|
CUtlVector< TSqlClass > m_QueuedRecords;
|
|
|
|
|
//schedules a write back to ensure we commit at least every N seconds
|
|
|
|
|
CScheduledFunction< CGCSQLWriteQueue< TSqlClass > > m_TimeCommit;
|
|
|
|
|
//maximum number of seconds between commits
|
|
|
|
|
uint32 m_nMaxMSToWrite;
|
|
|
|
|
//maximum number of records to buffer before writing back
|
|
|
|
|
uint32 m_nMaxToCache;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} //namespace GCSDK
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|