SCHEDULE.CPP
//==========================================================================; 
// 
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR 
//  PURPOSE. 
// 
//  Copyright (c) 1996 - 1997 Microsoft Corporation.  All Rights Reserved. 
// 
//--------------------------------------------------------------------------; 
//  SCHEDULE.CPP 
 
#include <streams.h> 
 
// DbgLog values (all on LOG_TIMING): 
// 
// 2 for schedulting, firing and shunting of events 
// 3 for wait delays and wake-up times of event thread 
// 4 for details of whats on the list when the thread awakes 
 
/* Construct & destructors */ 
 
CAMSchedule::CAMSchedule( HANDLE ev ) 
: CBaseObject(TEXT("CAMSchedule")) 
, head(&z, 0), z(0, MAX_TIME) 
, m_dwNextCookie(0), m_dwAdviseCount(0) 
, m_pAdviseCache(0), m_dwCacheCount(0) 
, m_ev( ev ) 
{ 
    head.m_dwAdviseCookie = z.m_dwAdviseCookie = 0; 
} 
 
CAMSchedule::~CAMSchedule() 
{ 
    m_Serialize.Lock(); 
 
    // Delete cache 
    CAdvisePacket * p = m_pAdviseCache; 
    while (p) 
    { 
        CAdvisePacket *const p_next = p->m_next; 
        delete p; 
        p = p_next; 
    } 
 
    ASSERT( m_dwAdviseCount == 0 ); 
    // Better to be safe than sorry 
    if ( m_dwAdviseCount > 0 ) 
    { 
        DumpLinkedList(); 
        while ( !head.m_next->IsZ() ) 
        { 
            head.DeleteNext(); 
            --m_dwAdviseCount; 
        } 
    } 
 
    // If, in the debug version, we assert twice, it means, not only 
    // did we have left over advises, but we have also let m_dwAdviseCount 
    // get out of sync. with the number of advises actually on the list. 
    ASSERT( m_dwAdviseCount == 0 ); 
 
    m_Serialize.Unlock(); 
} 
 
/* Public methods */ 
 
DWORD CAMSchedule::GetAdviseCount() 
{ 
    // No need to lock, m_dwAdviseCount is 32bits & declared volatile 
    return m_dwAdviseCount; 
} 
 
REFERENCE_TIME CAMSchedule::GetNextAdviseTime() 
{ 
    CAutoLock lck(&m_Serialize); // Need to stop the linked list from changing 
    return head.m_next->m_rtEventTime; 
} 
 
DWORD CAMSchedule::AddAdvisePacket 
( const REFERENCE_TIME & time1 
, const REFERENCE_TIME & time2 
, HANDLE h, BOOL periodic 
) 
{ 
    // Since we use MAX_TIME as a sentry, we can't afford to 
    // schedule a notification at MAX_TIME 
    ASSERT( time1 < MAX_TIME ); 
    DWORD Result; 
    CAdvisePacket * p; 
 
    m_Serialize.Lock(); 
 
    if (m_pAdviseCache) 
    { 
        p = m_pAdviseCache; 
        m_pAdviseCache = p->m_next; 
        --m_dwCacheCount; 
    } 
    else 
    { 
        p = new CAdvisePacket(); 
    } 
    if (p) 
    { 
        p->m_rtEventTime = time1; p->m_rtPeriod = time2; 
        p->m_hNotify = h; p->m_bPeriodic = periodic; 
        Result = AddAdvisePacket( p ); 
    } 
    else Result = 0; 
 
    m_Serialize.Unlock(); 
 
    return Result; 
} 
 
HRESULT CAMSchedule::Unadvise(DWORD dwAdviseCookie) 
{ 
    HRESULT hr = S_FALSE; 
    CAdvisePacket * p_prev = &head; 
    CAdvisePacket * p_n; 
    m_Serialize.Lock(); 
    while ( p_n = p_prev->Next() ) // The Next() method returns NULL when it hits z 
    { 
        if ( p_n->m_dwAdviseCookie == dwAdviseCookie ) 
        { 
            Delete( p_prev->RemoveNext() ); 
            --m_dwAdviseCount; 
            hr = S_OK; 
    // Having found one cookie that matches, there should be no more 
            #ifdef DEBUG 
       while (p_n = p_prev->Next()) 
               { 
                   ASSERT(p_n->m_dwAdviseCookie != dwAdviseCookie); 
                   p_prev = p_n; 
               } 
            #endif 
            break; 
        } 
        p_prev = p_n; 
    }; 
    m_Serialize.Unlock(); 
    return hr; 
} 
 
REFERENCE_TIME CAMSchedule::Advise( const REFERENCE_TIME & rtTime ) 
{ 
    REFERENCE_TIME  rtNextTime; 
    CAdvisePacket * pAdvise; 
 
    DbgLog((LOG_TIMING, 2, 
        TEXT("CAMSchedule::Advise( %lu ms )"), ULONG(rtTime / (UNITS / MILLISECONDS)))); 
 
    m_Serialize.Lock(); 
 
    #ifdef DEBUG 
        if (DbgCheckModuleLevel(LOG_TIMING, 4)) DumpLinkedList(); 
    #endif 
 
    REFERENCE_TIME rtLate, rtPrevLate = MAX_TIME; 
    while ( (rtLate = rtTime - (rtNextTime = (pAdvise=head.m_next)->m_rtEventTime)) >= 0 ) 
    { 
        ASSERT(pAdvise->m_dwAdviseCookie); // If this is zero, its the head or the tail!! 
        ASSERT( rtLate <= rtPrevLate );    // If we dispatch several, the later ones should 
                                           // not be as late as the earlier ones.  The ASSERT 
                                           // therefore is checking that our sheduling logic 
                                           // placed the packet in the right place. 
rtPrevLate = rtLate; 
        rtLate /= 10000; 
        DbgLog((LOG_TIMING, 2, 
            TEXT("CAMSchedule::Advise() Dispatching advise %lu for time stamp: %lu ms (%lu ms late)"), 
      pAdvise->m_dwAdviseCookie, ULONG(pAdvise->m_rtEventTime / (UNITS / MILLISECONDS)), ULONG(rtLate) )); 
 
        ASSERT(pAdvise->m_hNotify != INVALID_HANDLE_VALUE); 
 
        if (pAdvise->m_bPeriodic == TRUE) 
        { 
            EXECUTE_ASSERT(ReleaseSemaphore(pAdvise->m_hNotify,1,NULL)); 
            pAdvise->m_rtEventTime += pAdvise->m_rtPeriod; 
            ShuntHead(); 
        } 
        else 
        { 
            ASSERT( pAdvise->m_bPeriodic == FALSE ); 
            EXECUTE_ASSERT(SetEvent(pAdvise->m_hNotify)); 
            --m_dwAdviseCount; 
            Delete( head.RemoveNext() ); 
        } 
 
    } 
 
    DbgLog((LOG_TIMING, 3, 
            TEXT("CAMSchedule::Advise() Next time stamp: %lu ms, for advise %lu."), 
            DWORD(rtNextTime / (UNITS / MILLISECONDS)), pAdvise->m_dwAdviseCookie )); 
 
    m_Serialize.Unlock(); 
 
    return rtNextTime; 
} 
 
/* Private methods */ 
 
DWORD CAMSchedule::AddAdvisePacket( CAdvisePacket * pPacket ) 
{ 
    ASSERT(pPacket->m_rtEventTime >= 0 && pPacket->m_rtEventTime < MAX_TIME); 
    ASSERT(CritCheckIn(&m_Serialize)); 
 
    CAdvisePacket * p_prev = &head; 
    CAdvisePacket * p_n; 
 
    const DWORD Result = pPacket->m_dwAdviseCookie = ++m_dwNextCookie; 
    // This relies on the fact that z is a sentry with a maximal m_rtEventTime 
    for(;;p_prev = p_n) 
    { 
        p_n = p_prev->m_next; 
        if ( p_n->m_rtEventTime >= pPacket->m_rtEventTime ) break; 
    } 
    p_prev->InsertAfter( pPacket ); 
    ++m_dwAdviseCount; 
 
    DbgLog((LOG_TIMING, 2, TEXT("Added advise %lu, for thread 0x%02X, scheduled at %lu"), 
    pPacket->m_dwAdviseCookie, GetCurrentThreadId(), (pPacket->m_rtEventTime / (UNITS / MILLISECONDS)) )); 
 
    // If packet added at the head, then clock needs to re-evaluate wait time. 
    if ( p_prev == &head ) SetEvent( m_ev ); 
 
    return Result; 
} 
 
void CAMSchedule::Delete( CAdvisePacket * pPacket ) 
{ 
    if ( m_dwCacheCount >= dwCacheMax ) delete pPacket; 
    else 
    { 
        m_Serialize.Lock(); 
        pPacket->m_next = m_pAdviseCache; 
        m_pAdviseCache = pPacket; 
        ++m_dwCacheCount; 
        m_Serialize.Unlock(); 
    } 
} 
 
 
// Takes the head of the list & repositions it 
void CAMSchedule::ShuntHead() 
{ 
    CAdvisePacket * p_prev = &head; 
    CAdvisePacket * p_n; 
 
    m_Serialize.Lock(); 
    CAdvisePacket *const pPacket = head.m_next; 
 
    // This will catch both an empty list, 
    // and if somehow a MAX_TIME time gets into the list 
    // (which would also break this method). 
    ASSERT( pPacket->m_rtEventTime < MAX_TIME ); 
 
    // This relies on the fact that z is a sentry with a maximal m_rtEventTime 
    for(;;p_prev = p_n) 
    { 
        p_n = p_prev->m_next; 
        if ( p_n->m_rtEventTime > pPacket->m_rtEventTime ) break; 
    } 
    // If p_prev == pPacket then we're already in the right place 
    if (p_prev != pPacket) 
    { 
        head.m_next = pPacket->m_next; 
        (p_prev->m_next = pPacket)->m_next = p_n; 
    } 
    #ifdef DEBUG 
        DbgLog((LOG_TIMING, 2, TEXT("Periodic advise %lu, shunted to %lu"), 
        pPacket->m_dwAdviseCookie, (pPacket->m_rtEventTime / (UNITS / MILLISECONDS)) )); 
    #endif 
    m_Serialize.Unlock(); 
} 
 
 
#ifdef DEBUG 
void CAMSchedule::DumpLinkedList() 
{ 
    m_Serialize.Lock(); 
    int i=0; 
    DbgLog((LOG_TIMING, 1, TEXT("CAMSchedule::DumpLinkedList() this = %08X"), DWORD(this) )); 
    for ( CAdvisePacket * p = &head 
        ; p 
        ; p = p->m_next         , i++ 
        ) 
    { 
        DbgLog((LOG_TIMING, 1, TEXT("Advise List # %lu, Cookie %d,  RefTime %lu"), 
            i, 
    p->m_dwAdviseCookie, 
    p->m_rtEventTime / (UNITS / MILLISECONDS) 
            )); 
    } 
    m_Serialize.Unlock(); 
} 
#endif