Microsoft-3D-Movie-Maker/kauai/SRC/CLOK.CPP
2022-05-03 16:31:19 -07:00

400 lines
11 KiB
C++

/* Copyright (c) Microsoft Corporation.
Licensed under the MIT License. */
/***************************************************************************
Author: ShonK
Project: Kauai
Reviewed:
Copyright (c) Microsoft Corporation
Clock class. Clocks provide timing and alarm functionality. Clocks get
CPU time by inserting themselves in the command handler list (attached
to the CEX). This causes CLOK::FCmdAll to be called every time a command
comes through the CEX. The time of a clock is updated only when
CLOK::FCmdAll is called. So the following code will not time the operation:
dtim = vclok.TimCur();
for (...)
{
...
}
dtim = vlok.TimCur() - dtim;
At the end of this, dtim will always be zero (unless CLOK::FCmdAll is
somehow called in the loop - in which case dtim still won't be the exact
timing of the loop). To do this type of timing use TsCurrent() or
TsCurrentSystem(). Clocks use TsCurrent(), so scaling the application
time scales all clocks.
Added feature: TimCur now takes an optional boolean parameter indicating
whether the time should be calculated or just the current value as
described above. The default is false (return the current value ...).
FSetAlarm also has an optional boolean specifying whether the alarm
time should be computed from the current app time or from the current
value (ie from TimCur(fTrue) or TimCur(fFalse)). The default is to use
TimCur(fFalse) (old behavior).
By default, if a clock's time is computed to exceed its next alarm's
time, the clock's time "slips" back to the alarm's time. Eg, if an
alarm is set for tim = 1000 and the app does something that takes a long
time, so that the next time thru the clock's FCmdAll the clocks time
is computed to be 1200, the clock's time is set back to 1000 and the
alarm is triggered. If the clock should not slip in this way, fclokNoSlip
should be specified in the constructor's grfclok parameter.
fclokReset means that the clock's time should be reset to 0 every
time the following commands come through the command queue:
cidKey, cidTrackMouse, cidMouseMove, any cid less than cidMinNoMenu.
Such a clock could be used for screen saver functionality or time-out
animations, etc.
WARNING: If alarms are not handled and set appropriately, the app can
sometimes get into a tight loop that the user can't break into.
Suppose a command handler "A" wants to do something every 1/10 of a second.
It sets up a clock and sets an alarm for 1/10 of a second from now.
When the alarm goes off, A sets the next alarm for 1/10 of a second from
now, does some work that takes 1/2 second (we're running on a slow
machine), then enqueues a command "cidFoo" to another object "B".
Assuming the command queue was empty, the next command processed by the
CEX is cidFoo. This causes the alarm to go off again (remember that alarms
go off during the FCmdAll call). And the whole process repeats. The
command queue is never empty in the main app loop, so we never check the
system event queue, so the user can sit there and hit the keyboard or play
with the mouse as much as they want and we will never see it. The way to
avoid this is to set the next alarm after the work is done, and better
yet, don't reset the alarm until all commands resulting from the alarm
have been processed. Handler A should do the following: do its work,
enqueue cidFoo to object B, enqueue cidBar to itself. When it gets cidBar,
it sets an alarm for 1/10 of a second into the future. This guarantees
a 1/10 second gap between then end of handling one alarm and starting
to handle the next alarm.
***************************************************************************/
#include "frame.h"
ASSERTNAME
RTCLASS(CLOK)
BEGIN_CMD_MAP_BASE(CLOK)
END_CMD_MAP(&CLOK::FCmdAll, pvNil, kgrfcmmAll)
const long kcmhlClok = kswMin; //put clocks at the head of the list
PCLOK CLOK::_pclokFirst;
/***************************************************************************
Constructor for the clock - just zeros the time. fclokReset specifies
that this clock should reset itself to zero on key or mouse input.
fclokNoSlip specifies that the clok should not let time slip.
***************************************************************************/
CLOK::CLOK(long hid, ulong grfclok) : CMH(hid)
{
_pclokNext = _pclokFirst;
_pclokFirst = this;
_timBase = _timCur = _dtimAlarm = 0;
_timNext = kluMax;
_tsBase = 0;
_grfclok = grfclok;
_pglalad = pvNil;
AssertThis(0);
}
/***************************************************************************
Destructor for a CLOK - remove it from the linked list of clocks.
***************************************************************************/
CLOK::~CLOK(void)
{
PCLOK *ppclok;
for (ppclok = &_pclokFirst; *ppclok != pvNil && *ppclok != this;
ppclok = &(*ppclok)->_pclokNext)
{
}
if (*ppclok == this)
*ppclok = _pclokNext;
else
Bug("clok not in linked list");
ReleasePpo(&_pglalad);
}
/***************************************************************************
Static method to find the first clok with the given id.
***************************************************************************/
PCLOK CLOK::PclokFromHid(long hid)
{
PCLOK pclok;
for (pclok = _pclokFirst; pvNil != pclok; pclok = pclok->_pclokNext)
{
AssertPo(pclok, 0);
if (pclok->Hid() == hid)
break;
}
return pclok;
}
/***************************************************************************
Static method to remove all references to the given CMH from the clok
ALAD lists.
***************************************************************************/
void CLOK::BuryCmh(PCMH pcmh)
{
PCLOK pclok;
for (pclok = _pclokFirst; pvNil != pclok; pclok = pclok->_pclokNext)
pclok->RemoveCmh(pcmh);
}
/***************************************************************************
Remove any alarms set by the given CMH.
***************************************************************************/
void CLOK::RemoveCmh(PCMH pcmh)
{
AssertThis(0);
ALAD *qalad;
long ialad;
if (pvNil == _pglalad)
return;
for (ialad = _pglalad->IvMac(); ialad-- > 0; )
{
qalad = (ALAD *)_pglalad->QvGet(ialad);
if (qalad->pcmh == pcmh)
_pglalad->Delete(ialad);
}
}
/***************************************************************************
Start the clock.
***************************************************************************/
void CLOK::Start(ulong tim)
{
AssertThis(0);
_timBase = _timCur = tim;
_dtimAlarm = 0;
_tsBase = TsCurrent();
vpcex->RemoveCmh(this, kcmhlClok);
vpcex->FAddCmh(this, kcmhlClok, kgrfcmmAll);
}
/***************************************************************************
Stop the clock. The time will no longer advance on this clock.
***************************************************************************/
void CLOK::Stop(void)
{
AssertThis(0);
vpcex->RemoveCmh(this, kcmhlClok);
}
/***************************************************************************
Return the current time. If fAdjustForDelay is true, the time is a more
accurate time, but is not synchronized to the alarms or to the command
stream. Normally, the clok's time is only updated when a command gets
processed by the command dispatcher. If fAdjustForDelay is false, the
last computed time value is returned, otherwise the time is computed
from the current application time.
***************************************************************************/
ulong CLOK::TimCur(bool fAdjustForDelay)
{
AssertThis(0);
if (!fAdjustForDelay)
return _timCur;
return _timBase + LuMulDiv(TsCurrent() - _tsBase, kdtimSecond, kdtsSecond);
}
/***************************************************************************
Set an alarm for the given time and for the given command handler.
Alarms are sorted in _decreasing_ order.
***************************************************************************/
bool CLOK::FSetAlarm(long dtim, PCMH pcmhNotify, long lwUser,
bool fAdjustForDelay)
{
AssertThis(0);
AssertIn(dtim, 0, kcbMax);
AssertNilOrPo(pcmhNotify, 0);
ALAD alad;
ALAD *qalad;
long ialad, ialadMin, ialadLim;
alad.pcmh = pcmhNotify;
alad.tim = TimCur(fAdjustForDelay) + LwMax(dtim, 1);
alad.lw = lwUser;
if (pvNil == _pglalad && pvNil == (_pglalad = GL::PglNew(size(ALAD), 1)))
return fFalse;
for (ialadMin = 0, ialadLim = _pglalad->IvMac(); ialadMin < ialadLim; )
{
ialad = (ialadMin + ialadLim) / 2;
qalad = (ALAD *)_pglalad->QvGet(ialad);
if (qalad->tim < alad.tim)
ialadLim = ialad;
else
ialadMin = ialad + 1;
}
if (!_pglalad->FInsert(ialadMin, &alad))
return fFalse;
if (_timNext > alad.tim)
_timNext = alad.tim;
return fTrue;
}
/***************************************************************************
Advance the clock and sound an alarm if one is due to go off. This
actually gets called every time through the command loop.
***************************************************************************/
bool CLOK::FCmdAll(PCMD pcmd)
{
AssertThis(0);
AssertVarMem(pcmd);
CMD cmd;
long ialad;
ALAD alad;
ulong tsCur, timCur;
_dtimAlarm = 0;
if (pcmd->cid == cidAlarm)
return fFalse;
tsCur = TsCurrent();
timCur = _timBase + LuMulDiv(tsCur - _tsBase, kdtimSecond, kdtsSecond);
if (_grfclok & fclokReset)
{
switch (pcmd->cid)
{
case cidKey:
case cidTrackMouse:
case cidMouseMove:
goto LReset;
default:
if (pcmd->cid < cidMinNoMenu)
{
LReset:
_tsBase = tsCur;
_timBase = 0;
timCur = 0;
}
break;
}
}
if (timCur < _timNext)
{
// just update the time
_timCur = timCur;
return fFalse;
}
// sound any alarms
for (;;)
{
if (pvNil == _pglalad || 0 > (ialad = _pglalad->IvMac() - 1))
{
_timNext = kluMax;
break;
}
_pglalad->Get(ialad, &alad);
if (alad.tim > timCur)
{
_timNext = alad.tim;
break;
}
_pglalad->Delete(ialad);
// adjust the current time
_timCur = alad.tim;
_timNext = kluMax;
if (timCur > alad.tim && !(_grfclok & fclokNoSlip))
{
// we've slipped
timCur = _timBase = alad.tim;
_tsBase = tsCur;
}
// send the alarm
ClearPb(&cmd, size(CMD));
cmd.cid = cidAlarm;
cmd.pcmh = alad.pcmh;
cmd.rglw[0] = Hid();
cmd.rglw[1] = alad.tim;
cmd.rglw[2] = alad.lw;
if (pvNil != alad.pcmh)
{
// tell the CMH that the alarm went off
AddRef();
Assert(_cactRef > 1, 0);
_dtimAlarm = timCur - _timCur;
alad.pcmh->FDoCmd(&cmd);
if (_cactRef == 1)
{
Release();
return fFalse;
}
Release();
AssertThis(0);
}
else
vpcex->EnqueueCmd(&cmd);
}
_timCur = timCur;
return fFalse;
}
#ifdef DEBUG
/***************************************************************************
Assert the validity of a CLOK.
***************************************************************************/
void CLOK::AssertValid(ulong grf)
{
CLOK_PAR::AssertValid(0);
AssertNilOrPo(_pglalad, 0);
Assert(_timCur <= _timNext, "_timNext too small");
Assert((_grfclok & fclokNoSlip) || _dtimAlarm == 0,
"_dtimAlarm should be 0");
}
/***************************************************************************
Mark memory for the CLOK.
***************************************************************************/
void CLOK::MarkMem(void)
{
AssertValid(0);
CLOK_PAR::MarkMem();
MarkMemObj(_pglalad);
}
/***************************************************************************
Static method to mark all the CLOKs
***************************************************************************/
void CLOK::MarkAllCloks(void)
{
PCLOK pclok;
for (pclok = _pclokFirst; pvNil != pclok; pclok = pclok->_pclokNext)
{
AssertPo(pclok, 0);
MarkMemObj(pclok);
}
}
#endif //DEBUG