| //------------------------------------------------------------------------------ | |
| // File: OutputQ.cpp | |
| // | |
| // Desc: DirectShow base classes - implements COutputQueue class used by an | |
| // output pin which may sometimes want to queue output samples on a | |
| // separate thread and sometimes call Receive() directly on the input | |
| // pin. | |
| // | |
| // Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. | |
| //------------------------------------------------------------------------------ | |
| #include <streams.h> | |
| // | |
| // COutputQueue Constructor : | |
| // | |
| // Determines if a thread is to be created and creates resources | |
| // | |
| // pInputPin - the downstream input pin we're queueing samples to | |
| // | |
| // phr - changed to a failure code if this function fails | |
| // (otherwise unchanges) | |
| // | |
| // bAuto - Ask pInputPin if it can block in Receive by calling | |
| // its ReceiveCanBlock method and create a thread if | |
| // it can block, otherwise not. | |
| // | |
| // bQueue - if bAuto == FALSE then we create a thread if and only | |
| // if bQueue == TRUE | |
| // | |
| // lBatchSize - work in batches of lBatchSize | |
| // | |
| // bBatchEact - Use exact batch sizes so don't send until the | |
| // batch is full or SendAnyway() is called | |
| // | |
| // lListSize - If we create a thread make the list of samples queued | |
| // to the thread have this size cache | |
| // | |
| // dwPriority - If we create a thread set its priority to this | |
| // | |
| COutputQueue::COutputQueue( | |
| IPin *pInputPin, // Pin to send stuff to | |
| __inout HRESULT *phr, // 'Return code' | |
| BOOL bAuto, // Ask pin if queue or not | |
| BOOL bQueue, // Send through queue | |
| LONG lBatchSize, // Batch | |
| BOOL bBatchExact, // Batch exactly to BatchSize | |
| LONG lListSize, | |
| DWORD dwPriority, | |
| bool bFlushingOpt // flushing optimization | |
| ) : m_lBatchSize(lBatchSize), | |
| m_bBatchExact(bBatchExact && (lBatchSize > 1)), | |
| m_hThread(NULL), | |
| m_hSem(NULL), | |
| m_List(NULL), | |
| m_pPin(pInputPin), | |
| m_ppSamples(NULL), | |
| m_lWaiting(0), | |
| m_evFlushComplete(FALSE, phr), | |
| m_pInputPin(NULL), | |
| m_bSendAnyway(FALSE), | |
| m_nBatched(0), | |
| m_bFlushing(FALSE), | |
| m_bFlushed(TRUE), | |
| m_bFlushingOpt(bFlushingOpt), | |
| m_bTerminate(FALSE), | |
| m_hEventPop(NULL), | |
| m_hr(S_OK) | |
| { | |
| ASSERT(m_lBatchSize > 0); | |
| if (FAILED(*phr)) { | |
| return; | |
| } | |
| // Check the input pin is OK and cache its IMemInputPin interface | |
| *phr = pInputPin->QueryInterface(IID_IMemInputPin, (void **)&m_pInputPin); | |
| if (FAILED(*phr)) { | |
| return; | |
| } | |
| // See if we should ask the downstream pin | |
| if (bAuto) { | |
| HRESULT hr = m_pInputPin->ReceiveCanBlock(); | |
| if (SUCCEEDED(hr)) { | |
| bQueue = hr == S_OK; | |
| } | |
| } | |
| // Create our sample batch | |
| m_ppSamples = new PMEDIASAMPLE[m_lBatchSize]; | |
| if (m_ppSamples == NULL) { | |
| *phr = E_OUTOFMEMORY; | |
| return; | |
| } | |
| // If we're queueing allocate resources | |
| if (bQueue) { | |
| DbgLog((LOG_TRACE, 2, TEXT("Creating thread for output pin"))); | |
| m_hSem = CreateSemaphore(NULL, 0, 0x7FFFFFFF, NULL); | |
| if (m_hSem == NULL) { | |
| DWORD dwError = GetLastError(); | |
| *phr = AmHresultFromWin32(dwError); | |
| return; | |
| } | |
| m_List = new CSampleList(NAME("Sample Queue List"), | |
| lListSize, | |
| FALSE // No lock | |
| ); | |
| if (m_List == NULL) { | |
| *phr = E_OUTOFMEMORY; | |
| return; | |
| } | |
| DWORD dwThreadId; | |
| m_hThread = CreateThread(NULL, | |
| 0, | |
| InitialThreadProc, | |
| (LPVOID)this, | |
| 0, | |
| &dwThreadId); | |
| if (m_hThread == NULL) { | |
| DWORD dwError = GetLastError(); | |
| *phr = AmHresultFromWin32(dwError); | |
| return; | |
| } | |
| SetThreadPriority(m_hThread, dwPriority); | |
| } else { | |
| DbgLog((LOG_TRACE, 2, TEXT("Calling input pin directly - no thread"))); | |
| } | |
| } | |
| // | |
| // COutputQueuee Destructor : | |
| // | |
| // Free all resources - | |
| // | |
| // Thread, | |
| // Batched samples | |
| // | |
| COutputQueue::~COutputQueue() | |
| { | |
| DbgLog((LOG_TRACE, 3, TEXT("COutputQueue::~COutputQueue"))); | |
| /* Free our pointer */ | |
| if (m_pInputPin != NULL) { | |
| m_pInputPin->Release(); | |
| } | |
| if (m_hThread != NULL) { | |
| { | |
| CAutoLock lck(this); | |
| m_bTerminate = TRUE; | |
| m_hr = S_FALSE; | |
| NotifyThread(); | |
| } | |
| DbgWaitForSingleObject(m_hThread); | |
| EXECUTE_ASSERT(CloseHandle(m_hThread)); | |
| // The thread frees the samples when asked to terminate | |
| ASSERT(m_List->GetCount() == 0); | |
| delete m_List; | |
| } else { | |
| FreeSamples(); | |
| } | |
| if (m_hSem != NULL) { | |
| EXECUTE_ASSERT(CloseHandle(m_hSem)); | |
| } | |
| delete [] m_ppSamples; | |
| } | |
| // | |
| // Call the real thread proc as a member function | |
| // | |
| DWORD WINAPI COutputQueue::InitialThreadProc(__in LPVOID pv) | |
| { | |
| HRESULT hrCoInit = CAMThread::CoInitializeHelper(); | |
| COutputQueue *pSampleQueue = (COutputQueue *)pv; | |
| DWORD dwReturn = pSampleQueue->ThreadProc(); | |
| if(hrCoInit == S_OK) { | |
| CoUninitialize(); | |
| } | |
| return dwReturn; | |
| } | |
| // | |
| // Thread sending the samples downstream : | |
| // | |
| // When there is nothing to do the thread sets m_lWaiting (while | |
| // holding the critical section) and then waits for m_hSem to be | |
| // set (not holding the critical section) | |
| // | |
| DWORD COutputQueue::ThreadProc() | |
| { | |
| while (TRUE) { | |
| BOOL bWait = FALSE; | |
| IMediaSample *pSample; | |
| LONG lNumberToSend; // Local copy | |
| NewSegmentPacket* ppacket; | |
| // | |
| // Get a batch of samples and send it if possible | |
| // In any case exit the loop if there is a control action | |
| // requested | |
| // | |
| { | |
| CAutoLock lck(this); | |
| while (TRUE) { | |
| if (m_bTerminate) { | |
| FreeSamples(); | |
| return 0; | |
| } | |
| if (m_bFlushing) { | |
| FreeSamples(); | |
| SetEvent(m_evFlushComplete); | |
| } | |
| // Get a sample off the list | |
| pSample = m_List->RemoveHead(); | |
| // inform derived class we took something off the queue | |
| if (m_hEventPop) { | |
| //DbgLog((LOG_TRACE,3,TEXT("Queue: Delivered SET EVENT"))); | |
| SetEvent(m_hEventPop); | |
| } | |
| if (pSample != NULL && | |
| !IsSpecialSample(pSample)) { | |
| // If its just a regular sample just add it to the batch | |
| // and exit the loop if the batch is full | |
| m_ppSamples[m_nBatched++] = pSample; | |
| if (m_nBatched == m_lBatchSize) { | |
| break; | |
| } | |
| } else { | |
| // If there was nothing in the queue and there's nothing | |
| // to send (either because there's nothing or the batch | |
| // isn't full) then prepare to wait | |
| if (pSample == NULL && | |
| (m_bBatchExact || m_nBatched == 0)) { | |
| // Tell other thread to set the event when there's | |
| // something do to | |
| ASSERT(m_lWaiting == 0); | |
| m_lWaiting++; | |
| bWait = TRUE; | |
| } else { | |
| // We break out of the loop on SEND_PACKET unless | |
| // there's nothing to send | |
| if (pSample == SEND_PACKET && m_nBatched == 0) { | |
| continue; | |
| } | |
| if (pSample == NEW_SEGMENT) { | |
| // now we need the parameters - we are | |
| // guaranteed that the next packet contains them | |
| ppacket = (NewSegmentPacket *) m_List->RemoveHead(); | |
| // we took something off the queue | |
| if (m_hEventPop) { | |
| //DbgLog((LOG_TRACE,3,TEXT("Queue: Delivered SET EVENT"))); | |
| SetEvent(m_hEventPop); | |
| } | |
| ASSERT(ppacket); | |
| } | |
| // EOS_PACKET falls through here and we exit the loop | |
| // In this way it acts like SEND_PACKET | |
| } | |
| break; | |
| } | |
| } | |
| if (!bWait) { | |
| // We look at m_nBatched from the client side so keep | |
| // it up to date inside the critical section | |
| lNumberToSend = m_nBatched; // Local copy | |
| m_nBatched = 0; | |
| } | |
| } | |
| // Wait for some more data | |
| if (bWait) { | |
| DbgWaitForSingleObject(m_hSem); | |
| continue; | |
| } | |
| // OK - send it if there's anything to send | |
| // We DON'T check m_bBatchExact here because either we've got | |
| // a full batch or we dropped through because we got | |
| // SEND_PACKET or EOS_PACKET - both of which imply we should | |
| // flush our batch | |
| if (lNumberToSend != 0) { | |
| long nProcessed; | |
| if (m_hr == S_OK) { | |
| ASSERT(!m_bFlushed); | |
| HRESULT hr = m_pInputPin->ReceiveMultiple(m_ppSamples, | |
| lNumberToSend, | |
| &nProcessed); | |
| /* Don't overwrite a flushing state HRESULT */ | |
| CAutoLock lck(this); | |
| if (m_hr == S_OK) { | |
| m_hr = hr; | |
| } | |
| ASSERT(!m_bFlushed); | |
| } | |
| while (lNumberToSend != 0) { | |
| m_ppSamples[--lNumberToSend]->Release(); | |
| } | |
| if (m_hr != S_OK) { | |
| // In any case wait for more data - S_OK just | |
| // means there wasn't an error | |
| DbgLog((LOG_ERROR, 2, TEXT("ReceiveMultiple returned %8.8X"), | |
| m_hr)); | |
| } | |
| } | |
| // Check for end of stream | |
| if (pSample == EOS_PACKET) { | |
| // We don't send even end of stream on if we've previously | |
| // returned something other than S_OK | |
| // This is because in that case the pin which returned | |
| // something other than S_OK should have either sent | |
| // EndOfStream() or notified the filter graph | |
| if (m_hr == S_OK) { | |
| DbgLog((LOG_TRACE, 2, TEXT("COutputQueue sending EndOfStream()"))); | |
| HRESULT hr = m_pPin->EndOfStream(); | |
| if (FAILED(hr)) { | |
| DbgLog((LOG_ERROR, 2, TEXT("COutputQueue got code 0x%8.8X from EndOfStream()"))); | |
| } | |
| } | |
| } | |
| // Data from a new source | |
| if (pSample == RESET_PACKET) { | |
| m_hr = S_OK; | |
| SetEvent(m_evFlushComplete); | |
| } | |
| if (pSample == NEW_SEGMENT) { | |
| m_pPin->NewSegment(ppacket->tStart, ppacket->tStop, ppacket->dRate); | |
| delete ppacket; | |
| } | |
| } | |
| } | |
| // Send batched stuff anyway | |
| void COutputQueue::SendAnyway() | |
| { | |
| if (!IsQueued()) { | |
| // m_bSendAnyway is a private parameter checked in ReceiveMultiple | |
| m_bSendAnyway = TRUE; | |
| LONG nProcessed; | |
| ReceiveMultiple(NULL, 0, &nProcessed); | |
| m_bSendAnyway = FALSE; | |
| } else { | |
| CAutoLock lck(this); | |
| QueueSample(SEND_PACKET); | |
| NotifyThread(); | |
| } | |
| } | |
| void | |
| COutputQueue::NewSegment( | |
| REFERENCE_TIME tStart, | |
| REFERENCE_TIME tStop, | |
| double dRate) | |
| { | |
| if (!IsQueued()) { | |
| if (S_OK == m_hr) { | |
| if (m_bBatchExact) { | |
| SendAnyway(); | |
| } | |
| m_pPin->NewSegment(tStart, tStop, dRate); | |
| } | |
| } else { | |
| if (m_hr == S_OK) { | |
| // | |
| // we need to queue the new segment to appear in order in the | |
| // data, but we need to pass parameters to it. Rather than | |
| // take the hit of wrapping every single sample so we can tell | |
| // special ones apart, we queue special pointers to indicate | |
| // special packets, and we guarantee (by holding the | |
| // critical section) that the packet immediately following a | |
| // NEW_SEGMENT value is a NewSegmentPacket containing the | |
| // parameters. | |
| NewSegmentPacket * ppack = new NewSegmentPacket; | |
| if (ppack == NULL) { | |
| return; | |
| } | |
| ppack->tStart = tStart; | |
| ppack->tStop = tStop; | |
| ppack->dRate = dRate; | |
| CAutoLock lck(this); | |
| QueueSample(NEW_SEGMENT); | |
| QueueSample( (IMediaSample*) ppack); | |
| NotifyThread(); | |
| } | |
| } | |
| } | |
| // | |
| // End of Stream is queued to output device | |
| // | |
| void COutputQueue::EOS() | |
| { | |
| CAutoLock lck(this); | |
| if (!IsQueued()) { | |
| if (m_bBatchExact) { | |
| SendAnyway(); | |
| } | |
| if (m_hr == S_OK) { | |
| DbgLog((LOG_TRACE, 2, TEXT("COutputQueue sending EndOfStream()"))); | |
| m_bFlushed = FALSE; | |
| HRESULT hr = m_pPin->EndOfStream(); | |
| if (FAILED(hr)) { | |
| DbgLog((LOG_ERROR, 2, TEXT("COutputQueue got code 0x%8.8X from EndOfStream()"))); | |
| } | |
| } | |
| } else { | |
| if (m_hr == S_OK) { | |
| m_bFlushed = FALSE; | |
| QueueSample(EOS_PACKET); | |
| NotifyThread(); | |
| } | |
| } | |
| } | |
| // | |
| // Flush all the samples in the queue | |
| // | |
| void COutputQueue::BeginFlush() | |
| { | |
| if (IsQueued()) { | |
| { | |
| CAutoLock lck(this); | |
| // block receives -- we assume this is done by the | |
| // filter in which we are a component | |
| // discard all queued data | |
| m_bFlushing = TRUE; | |
| // Make sure we discard all samples from now on | |
| if (m_hr == S_OK) { | |
| m_hr = S_FALSE; | |
| } | |
| // Optimize so we don't keep calling downstream all the time | |
| if (m_bFlushed && m_bFlushingOpt) { | |
| return; | |
| } | |
| // Make sure we really wait for the flush to complete | |
| m_evFlushComplete.Reset(); | |
| NotifyThread(); | |
| } | |
| // pass this downstream | |
| m_pPin->BeginFlush(); | |
| } else { | |
| // pass downstream first to avoid deadlocks | |
| m_pPin->BeginFlush(); | |
| CAutoLock lck(this); | |
| // discard all queued data | |
| m_bFlushing = TRUE; | |
| // Make sure we discard all samples from now on | |
| if (m_hr == S_OK) { | |
| m_hr = S_FALSE; | |
| } | |
| } | |
| } | |
| // | |
| // leave flush mode - pass this downstream | |
| void COutputQueue::EndFlush() | |
| { | |
| { | |
| CAutoLock lck(this); | |
| ASSERT(m_bFlushing); | |
| if (m_bFlushingOpt && m_bFlushed && IsQueued()) { | |
| m_bFlushing = FALSE; | |
| m_hr = S_OK; | |
| return; | |
| } | |
| } | |
| // sync with pushing thread -- done in BeginFlush | |
| // ensure no more data to go downstream -- done in BeginFlush | |
| // | |
| // Because we are synching here there is no need to hold the critical | |
| // section (in fact we'd deadlock if we did!) | |
| if (IsQueued()) { | |
| m_evFlushComplete.Wait(); | |
| } else { | |
| FreeSamples(); | |
| } | |
| // Be daring - the caller has guaranteed no samples will arrive | |
| // before EndFlush() returns | |
| m_bFlushing = FALSE; | |
| m_bFlushed = TRUE; | |
| // call EndFlush on downstream pins | |
| m_pPin->EndFlush(); | |
| m_hr = S_OK; | |
| } | |
| // COutputQueue::QueueSample | |
| // | |
| // private method to Send a sample to the output queue | |
| // The critical section MUST be held when this is called | |
| void COutputQueue::QueueSample(IMediaSample *pSample) | |
| { | |
| if (NULL == m_List->AddTail(pSample)) { | |
| if (!IsSpecialSample(pSample)) { | |
| pSample->Release(); | |
| } | |
| } | |
| } | |
| // | |
| // COutputQueue::Receive() | |
| // | |
| // Send a single sample by the multiple sample route | |
| // (NOTE - this could be optimized if necessary) | |
| // | |
| // On return the sample will have been Release()'d | |
| // | |
| HRESULT COutputQueue::Receive(IMediaSample *pSample) | |
| { | |
| LONG nProcessed; | |
| return ReceiveMultiple(&pSample, 1, &nProcessed); | |
| } | |
| // | |
| // COutputQueue::ReceiveMultiple() | |
| // | |
| // Send a set of samples to the downstream pin | |
| // | |
| // ppSamples - array of samples | |
| // nSamples - how many | |
| // nSamplesProcessed - How many were processed | |
| // | |
| // On return all samples will have been Release()'d | |
| // | |
| HRESULT COutputQueue::ReceiveMultiple ( | |
| __in_ecount(nSamples) IMediaSample **ppSamples, | |
| long nSamples, | |
| __out long *nSamplesProcessed) | |
| { | |
| if (nSamples < 0) { | |
| return E_INVALIDARG; | |
| } | |
| CAutoLock lck(this); | |
| // Either call directly or queue up the samples | |
| if (!IsQueued()) { | |
| // If we already had a bad return code then just return | |
| if (S_OK != m_hr) { | |
| // If we've never received anything since the last Flush() | |
| // and the sticky return code is not S_OK we must be | |
| // flushing | |
| // ((!A || B) is equivalent to A implies B) | |
| ASSERT(!m_bFlushed || m_bFlushing); | |
| // We're supposed to Release() them anyway! | |
| *nSamplesProcessed = 0; | |
| for (int i = 0; i < nSamples; i++) { | |
| DbgLog((LOG_TRACE, 3, TEXT("COutputQueue (direct) : Discarding %d samples code 0x%8.8X"), | |
| nSamples, m_hr)); | |
| ppSamples[i]->Release(); | |
| } | |
| return m_hr; | |
| } | |
| // | |
| // If we're flushing the sticky return code should be S_FALSE | |
| // | |
| ASSERT(!m_bFlushing); | |
| m_bFlushed = FALSE; | |
| ASSERT(m_nBatched < m_lBatchSize); | |
| ASSERT(m_nBatched == 0 || m_bBatchExact); | |
| // Loop processing the samples in batches | |
| LONG iLost = 0; | |
| long iDone = 0; | |
| for (iDone = 0; | |
| iDone < nSamples || (m_nBatched != 0 && m_bSendAnyway); | |
| ) { | |
| //pragma message (REMIND("Implement threshold scheme")) | |
| ASSERT(m_nBatched < m_lBatchSize); | |
| if (iDone < nSamples) { | |
| m_ppSamples[m_nBatched++] = ppSamples[iDone++]; | |
| } | |
| if (m_nBatched == m_lBatchSize || | |
| nSamples == 0 && (m_bSendAnyway || !m_bBatchExact)) { | |
| LONG nDone; | |
| DbgLog((LOG_TRACE, 4, TEXT("Batching %d samples"), | |
| m_nBatched)); | |
| if (m_hr == S_OK) { | |
| m_hr = m_pInputPin->ReceiveMultiple(m_ppSamples, | |
| m_nBatched, | |
| &nDone); | |
| } else { | |
| nDone = 0; | |
| } | |
| iLost += m_nBatched - nDone; | |
| for (LONG i = 0; i < m_nBatched; i++) { | |
| m_ppSamples[i]->Release(); | |
| } | |
| m_nBatched = 0; | |
| } | |
| } | |
| *nSamplesProcessed = iDone - iLost; | |
| if (*nSamplesProcessed < 0) { | |
| *nSamplesProcessed = 0; | |
| } | |
| return m_hr; | |
| } else { | |
| /* We're sending to our thread */ | |
| if (m_hr != S_OK) { | |
| *nSamplesProcessed = 0; | |
| DbgLog((LOG_TRACE, 3, TEXT("COutputQueue (queued) : Discarding %d samples code 0x%8.8X"), | |
| nSamples, m_hr)); | |
| for (int i = 0; i < nSamples; i++) { | |
| ppSamples[i]->Release(); | |
| } | |
| return m_hr; | |
| } | |
| m_bFlushed = FALSE; | |
| for (long i = 0; i < nSamples; i++) { | |
| QueueSample(ppSamples[i]); | |
| } | |
| *nSamplesProcessed = nSamples; | |
| if (!m_bBatchExact || | |
| m_nBatched + m_List->GetCount() >= m_lBatchSize) { | |
| NotifyThread(); | |
| } | |
| return S_OK; | |
| } | |
| } | |
| // Get ready for new data - cancels sticky m_hr | |
| void COutputQueue::Reset() | |
| { | |
| if (!IsQueued()) { | |
| m_hr = S_OK; | |
| } else { | |
| { | |
| CAutoLock lck(this); | |
| QueueSample(RESET_PACKET); | |
| NotifyThread(); | |
| } | |
| m_evFlushComplete.Wait(); | |
| } | |
| } | |
| // Remove and Release() all queued and Batched samples | |
| void COutputQueue::FreeSamples() | |
| { | |
| CAutoLock lck(this); | |
| if (IsQueued()) { | |
| while (TRUE) { | |
| IMediaSample *pSample = m_List->RemoveHead(); | |
| // inform derived class we took something off the queue | |
| if (m_hEventPop) { | |
| //DbgLog((LOG_TRACE,3,TEXT("Queue: Delivered SET EVENT"))); | |
| SetEvent(m_hEventPop); | |
| } | |
| if (pSample == NULL) { | |
| break; | |
| } | |
| if (!IsSpecialSample(pSample)) { | |
| pSample->Release(); | |
| } else { | |
| if (pSample == NEW_SEGMENT) { | |
| // Free NEW_SEGMENT packet | |
| NewSegmentPacket *ppacket = | |
| (NewSegmentPacket *) m_List->RemoveHead(); | |
| // inform derived class we took something off the queue | |
| if (m_hEventPop) { | |
| //DbgLog((LOG_TRACE,3,TEXT("Queue: Delivered SET EVENT"))); | |
| SetEvent(m_hEventPop); | |
| } | |
| ASSERT(ppacket != NULL); | |
| delete ppacket; | |
| } | |
| } | |
| } | |
| } | |
| for (int i = 0; i < m_nBatched; i++) { | |
| m_ppSamples[i]->Release(); | |
| } | |
| m_nBatched = 0; | |
| } | |
| // Notify the thread if there is something to do | |
| // | |
| // The critical section MUST be held when this is called | |
| void COutputQueue::NotifyThread() | |
| { | |
| // Optimize - no need to signal if it's not waiting | |
| ASSERT(IsQueued()); | |
| if (m_lWaiting) { | |
| ReleaseSemaphore(m_hSem, m_lWaiting, NULL); | |
| m_lWaiting = 0; | |
| } | |
| } | |
| // See if there's any work to do | |
| // Returns | |
| // TRUE if there is nothing on the queue and nothing in the batch | |
| // and all data has been sent | |
| // FALSE otherwise | |
| // | |
| BOOL COutputQueue::IsIdle() | |
| { | |
| CAutoLock lck(this); | |
| // We're idle if | |
| // there is no thread (!IsQueued()) OR | |
| // the thread is waiting for more work (m_lWaiting != 0) | |
| // AND | |
| // there's nothing in the current batch (m_nBatched == 0) | |
| if (IsQueued() && m_lWaiting == 0 || m_nBatched != 0) { | |
| return FALSE; | |
| } else { | |
| // If we're idle it shouldn't be possible for there | |
| // to be anything on the work queue | |
| ASSERT(!IsQueued() || m_List->GetCount() == 0); | |
| return TRUE; | |
| } | |
| } | |
| void COutputQueue::SetPopEvent(HANDLE hEvent) | |
| { | |
| m_hEventPop = hEvent; | |
| } |