You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
493 lines
14 KiB
493 lines
14 KiB
/***************************************************************************\
|
|
*
|
|
* 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<Action> * 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<Action *> 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<Action *> 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<Action> * 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<Action *> & 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<Action *> & 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));
|
|
}
|
|
|