/***************************************************************************\ * * File: Scheduler.cpp * * Description: * Scheduler.cpp maintains a collection of timers that are created and used * by the application for notifications. * * * History: * 1/18/2000: JStall: Created * * Copyright (C) 2000 by Microsoft Corporation. All rights reserved. * \***************************************************************************/ #include "stdafx.h" #include "Motion.h" #include "Scheduler.h" #include "Action.h" #include "Context.h" /***************************************************************************\ ***************************************************************************** * * class Scheduler * ***************************************************************************** \***************************************************************************/ //--------------------------------------------------------------------------- Scheduler::Scheduler() { #if DBG m_DEBUG_fLocked = FALSE; #endif // DBG } //--------------------------------------------------------------------------- Scheduler::~Scheduler() { AssertMsg(m_fShutdown, "Scheduler must be manually shutdown before destruction"); } /***************************************************************************\ * * Scheduler::xwPreDestroy * * xwPreDestroy() prepares the Scheduler for destruction while it is still * valid to callback into the application. * \***************************************************************************/ void Scheduler::xwPreDestroy() { m_fShutdown = TRUE; xwRemoveAllActions(); } /***************************************************************************\ * * Scheduler::AddAction * * AddAction() creates and adds a new Action, using the specified information. * \***************************************************************************/ Action * Scheduler::AddAction( IN const GMA_ACTION * pma) // Action information { // // Check if shutting down and don't allow any new Actions to be created. // if (m_fShutdown) { return NULL; } Action * pact; Enter(); // // Determine which list to add the action to and add it. // GList * plstParent = NULL; bool fPresent = IsPresentTime(pma->flDelay); if (fPresent) { plstParent = &m_lstacPresent; } else { plstParent = &m_lstacFuture; } DWORD dwCurTick = GetTickCount(); pact = Action::Build(plstParent, pma, dwCurTick, fPresent); if (pact == NULL) { goto Exit; } plstParent->Add(pact); // // Returning out the Action, so we need to lock the HACTION that we are // giving back. // pact->Lock(); Exit: Leave(); return pact; } /***************************************************************************\ * * Scheduler::xwRemoveAllActions * * xwRemoveAllActions() removes all Actions still "owned" by the Scheduler. * \***************************************************************************/ void Scheduler::xwRemoveAllActions() { GArrayF aracFire; // // NOTE: We can not fire any notifications while inside the Scheduler lock, // or the Scheduler could get messed up. Instead, we need to remember all // of the Actions to fire, and then fire them when we leave the lock. // Enter(); int cItems = m_lstacPresent.GetSize() + m_lstacFuture.GetSize(); aracFire.SetSize(cItems); int idxAdd = 0; while (!m_lstacPresent.IsEmpty()) { Action * pact = m_lstacPresent.UnlinkHead(); VerifyMsg(pact->xwUnlock(), "Action should still be valid"); pact->SetParent(NULL); aracFire[idxAdd++] = pact; } while (!m_lstacFuture.IsEmpty()) { Action * pact = m_lstacFuture.UnlinkHead(); VerifyMsg(pact->xwUnlock(), "Action should still be valid"); pact->SetParent(NULL); aracFire[idxAdd++] = pact; } AssertMsg(idxAdd == cItems, "Should have added all items"); Leave(); // // Don't fire from processing when removing the Actions. Instead, only // have the destructors fire when the Action finally gets cleaned up. // xwFireNL(aracFire, FALSE); } /***************************************************************************\ * * Scheduler::xwProcessActionsNL * * xwProcessActionsNL() processes the Actions for one iteration, moving * between queues and firing notifications. * \***************************************************************************/ DWORD Scheduler::xwProcessActionsNL() { DWORD dwCurTime = ::GetTickCount(); // // NOTE: We need to leave the lock when calling back as part of the // Action::Fire() mechanism. To accomplish this, we store up all of the // Actions to callback during processing and callback after leaving the // lock. // // NOTE: We can not use a GList to store the actions to fire because they // are already stored in a list and the ListNode's would conflict. So, // we use an Array instead. // GArrayF aracFire; Enter(); Thread * pCurThread = GetThread(); BOOL fFinishedPeriod, fFire; // // Go through and pre-process all future actions. If a future actions // time has come up, move it to the present actions list. // Action * pactCur = m_lstacFuture.GetHead(); while (pactCur != NULL) { Action * pactNext = pactCur->GetNext(); if (pactCur->GetThread() == pCurThread) { AssertMsg(!pactCur->IsPresent(), "Ensure action not yet present"); pactCur->Process(dwCurTime, &fFinishedPeriod, &fFire); AssertMsg(! fFire, "Should not fire future Actions"); if (fFinishedPeriod) { // // Action has reached the present // m_lstacFuture.Unlink(pactCur); pactCur->SetPresent(TRUE); pactCur->ResetPresent(dwCurTime); pactCur->SetParent(&m_lstacPresent); m_lstacPresent.Add(pactCur); } } pactCur = pactNext; } // // Go through and process all present actions // pactCur = m_lstacPresent.GetHead(); while (pactCur != NULL) { Action * pactNext = pactCur->GetNext(); if (pactCur->GetThread() == pCurThread) { pactCur->Process(dwCurTime, &fFinishedPeriod, &fFire); if (fFire) { // // The Action should be fired, so lock it and add it to the // delayed set of Actions to fire. It is important to lock // it if the Action is finished so that it doesn't get // destroyed. // pactCur->Lock(); if (aracFire.Add(pactCur) < 0) { // TODO: Unable to add the Action. This is pretty bad. // Need to figure out how to handle this situation, // especially if fFinishedPeriod or the app may leak resources. } } if (fFinishedPeriod) { pactCur->SetParent(NULL); m_lstacPresent.Unlink(pactCur); pactCur->EndPeriod(); // // The action has finished this round. If it is not periodic, it // will be destroyed during its callback. If it is periodic, // need to re-add it to the correct (present or future) list. // if (pactCur->IsComplete()) { pactCur->MarkDelete(TRUE); VerifyMsg(pactCur->xwUnlock(), "Should still have HANDLE lock"); } else { GList * plstParent = NULL; float flWait = pactCur->GetStartDelay(); BOOL fPresent = IsPresentTime(flWait); if (fPresent) { pactCur->ResetPresent(dwCurTime); plstParent = &m_lstacPresent; } else { pactCur->ResetFuture(dwCurTime, FALSE); plstParent = &m_lstacFuture; } pactCur->SetPresent(fPresent); pactCur->SetParent(plstParent); plstParent->Add(pactCur); } } } pactCur = pactNext; } // // Now that everything has been determined, determine how long until Actions // need to be processed again. // // NOTE: To keep Actions from overwhelming CPU and giving other tasks some // time to accumulate and process, we normally limit the granularity to // 10 ms. We actually should allow Actions to specify there own granularity // and provide a default, probably of 10 ms for continuous Actions. // // NOTE: Is is very important that this number is not too high, because it // will severly limit the framerate to 1000 / delay. After doing // significant profiling work, 10 ms was found to be ideal which gives an // upper bound of about 100 fps. // DWORD dwTimeOut = INFINITE; if (m_lstacPresent.IsEmpty()) { // // There are no present Actions, so check over the future Actions to // determine when the next one executes. // Action * pactCur = m_lstacFuture.GetHead(); while (pactCur != NULL) { Action * pactNext = pactCur->GetNext(); if (pactCur->GetThread() == pCurThread) { AssertMsg(!pactCur->IsPresent(), "Ensure action not yet present"); DWORD dwNewTimeOut = pactCur->GetIdleTimeOut(dwCurTime); AssertMsg(dwTimeOut > 0, "If Action has no TimeOut, should already be present."); if (dwNewTimeOut < dwTimeOut) { dwTimeOut = dwNewTimeOut; } } pactCur = pactNext; } } else { // // There are present Actions, so query their PauseTimeOut(). // Action * pactCur = m_lstacPresent.GetHead(); while (pactCur != NULL) { Action * pactNext = pactCur->GetNext(); DWORD dwNewTimeout = pactCur->GetPauseTimeOut(); if (dwNewTimeout < dwTimeOut) { dwTimeOut = dwNewTimeout; if (dwTimeOut == 0) { break; } } pactCur = pactNext; } } Leave(); xwFireNL(aracFire, TRUE); // // After actually execution the Actions, compute how much time to wait until // processing the next batch. We want to subtract the time we spent // processing the Actions, since if we setup timers on 50 ms intervals and // the processing takes 20 ms, we should only wait 30 ms. // // NOTE we need to do this AFTER calling xwFireNL(), since this fires the // actual notifications and does the processing. If we compute before this, // the majority of the work will not be included. // DWORD dwOldCurTime = dwCurTime; dwCurTime = ::GetTickCount(); // Update the current time DWORD dwProcessTime = ComputeTickDelta(dwCurTime, dwOldCurTime); if (dwProcessTime < dwTimeOut) { dwTimeOut -= dwProcessTime; } else { dwTimeOut = 0; } return dwTimeOut; } //--------------------------------------------------------------------------- #if DBG void DEBUG_CheckValid(const GArrayF & aracFire, int idxStart) { int cActions = aracFire.GetSize(); for (int i = idxStart; i < cActions; i++) { DWORD * pdw = (DWORD *) aracFire[i]; AssertMsg(*pdw != 0xfeeefeee, "Should still be valid"); } } #endif // DBG /***************************************************************************\ * * Scheduler::xwFireNL * * xwFireNL() fires notifications for the specified Actions, updating Action * state as it is fired. * \***************************************************************************/ void Scheduler::xwFireNL( IN GArrayF & aracFire, // Actions to notify IN BOOL fFire // "Fire" the notification (or just update) ) const { #if DBG // // Check that each Action is only in the list once. // { int cActions = aracFire.GetSize(); for (int i = 0; i < cActions; i++) { aracFire[i]->DEBUG_MarkInFire(TRUE); for (int j = i + 1; j < cActions; j++) { AssertMsg(aracFire[i] != aracFire[j], "Should only be in once"); } } DEBUG_CheckValid(aracFire, 0); } #endif // DBG // // Outside of the lock, so can fire the callbacks. // // NOTE: We may actually be locked by a different thread, but that's okay. // int cActions = aracFire.GetSize(); for (int idx = 0; idx < cActions; idx++) { Action * pact = aracFire[idx]; #if DBG DEBUG_CheckValid(aracFire, idx); #endif // DBG if (fFire) { pact->xwFireNL(); } #if DBG aracFire[idx]->DEBUG_MarkInFire(FALSE); #endif // DBG pact->xwUnlock(); #if DBG aracFire[idx] = NULL; #endif // DBG } // // NOTE: Since we pass in a Action * array, we don't need to worry about // the destructors getting called and the Actions being incorrectly // destroyed. // } /***************************************************************************\ ***************************************************************************** * * Global Functions * ***************************************************************************** \***************************************************************************/ //--------------------------------------------------------------------------- HACTION GdCreateAction(const GMA_ACTION * pma) { return (HACTION) GetHandle(GetMotionSC()->GetScheduler()->AddAction(pma)); }