mirror of
https://github.com/scummvm/scummvm.git
synced 2026-06-20 05:45:29 +00:00
pause/resume need to treat things a bit differently from the normal setRate function. This caused a jump in a timer when resuming from the pause menu in certain cases, though not all the time.
Regression from b50cac637e, which was part of the fix for the lid animations.
423 lines
10 KiB
C++
423 lines
10 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* Additional copyright for this file:
|
|
* Copyright (C) 1995-1997 Presto Studios, Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include "pegasus/pegasus.h"
|
|
#include "pegasus/notification.h"
|
|
#include "pegasus/timers.h"
|
|
|
|
namespace Pegasus {
|
|
|
|
Idler::Idler() {
|
|
_isIdling = false;
|
|
_nextIdler = 0;
|
|
_prevIdler = 0;
|
|
}
|
|
|
|
Idler::~Idler() {
|
|
stopIdling();
|
|
}
|
|
|
|
void Idler::startIdling() {
|
|
if (!isIdling()) {
|
|
((PegasusEngine *)g_engine)->addIdler(this);
|
|
_isIdling = true;
|
|
}
|
|
}
|
|
|
|
void Idler::stopIdling() {
|
|
if (isIdling()) {
|
|
((PegasusEngine *)g_engine)->removeIdler(this);
|
|
_isIdling = false;
|
|
}
|
|
}
|
|
|
|
TimeBase::TimeBase(const TimeScale preferredScale) {
|
|
_preferredScale = preferredScale;
|
|
_callBackList = 0;
|
|
_paused = false;
|
|
_flags = 0;
|
|
_lastMillis = 0;
|
|
_time = 0;
|
|
_rate = 0;
|
|
_startTime = 0;
|
|
_startScale = 1;
|
|
_stopTime = 0xffffffff;
|
|
_stopScale = 1;
|
|
_master = 0;
|
|
_pausedRate = 0;
|
|
_pauseStart = 0;
|
|
|
|
((PegasusEngine *)g_engine)->addTimeBase(this);
|
|
}
|
|
|
|
TimeBase::~TimeBase() {
|
|
((PegasusEngine *)g_engine)->removeTimeBase(this);
|
|
disposeAllCallBacks();
|
|
}
|
|
|
|
void TimeBase::setTime(const TimeValue time, const TimeScale scale) {
|
|
_time = Common::Rational(time, (scale == 0) ? _preferredScale : scale);
|
|
_lastMillis = 0;
|
|
}
|
|
|
|
TimeValue TimeBase::getTime(const TimeScale scale) {
|
|
// HACK: Emulate the master TimeBase code here for the one case that needs it in the
|
|
// game. Note that none of the master TimeBase code in this file should actually be
|
|
// used as a reference for anything ever.
|
|
if (_master)
|
|
return _master->getTime(scale);
|
|
|
|
return _time.getNumerator() * ((scale == 0) ? _preferredScale : scale) / _time.getDenominator();
|
|
}
|
|
|
|
void TimeBase::setRate(const Common::Rational rate) {
|
|
_rate = rate;
|
|
_lastMillis = 0;
|
|
|
|
if (_rate == 0)
|
|
_paused = false;
|
|
}
|
|
|
|
void TimeBase::start() {
|
|
if (_paused)
|
|
_pausedRate = 1;
|
|
else
|
|
setRate(1);
|
|
}
|
|
|
|
void TimeBase::stop() {
|
|
setRate(0);
|
|
_paused = false;
|
|
}
|
|
|
|
void TimeBase::pause() {
|
|
if (isRunning() && !_paused) {
|
|
_pausedRate = getRate();
|
|
_rate = 0;
|
|
_paused = true;
|
|
_pauseStart = g_system->getMillis();
|
|
}
|
|
}
|
|
|
|
void TimeBase::resume() {
|
|
if (_paused) {
|
|
_rate = _pausedRate;
|
|
_paused = false;
|
|
|
|
if (isRunning())
|
|
_lastMillis += g_system->getMillis() - _pauseStart;
|
|
}
|
|
}
|
|
|
|
bool TimeBase::isRunning() {
|
|
if (_paused && _pausedRate != 0)
|
|
return true;
|
|
|
|
Common::Rational rate = getRate();
|
|
|
|
if (rate == 0)
|
|
return false;
|
|
|
|
if (getFlags() & kLoopTimeBase)
|
|
return true;
|
|
|
|
if (rate > 0)
|
|
return getTime() != getStop();
|
|
|
|
return getTime() != getStart();
|
|
}
|
|
|
|
void TimeBase::setStart(const TimeValue startTime, const TimeScale scale) {
|
|
_startTime = startTime;
|
|
_startScale = (scale == 0) ? _preferredScale : scale;
|
|
}
|
|
|
|
TimeValue TimeBase::getStart(const TimeScale scale) const {
|
|
if (scale)
|
|
return _startTime * scale / _startScale;
|
|
|
|
return _startTime * _preferredScale / _startScale;
|
|
}
|
|
|
|
void TimeBase::setStop(const TimeValue stopTime, const TimeScale scale) {
|
|
_stopTime = stopTime;
|
|
_stopScale = (scale == 0) ? _preferredScale : scale;
|
|
}
|
|
|
|
TimeValue TimeBase::getStop(const TimeScale scale) const {
|
|
if (scale)
|
|
return _stopTime * scale / _stopScale;
|
|
|
|
return _stopTime * _preferredScale / _stopScale;
|
|
}
|
|
|
|
void TimeBase::setSegment(const TimeValue startTime, const TimeValue stopTime, const TimeScale scale) {
|
|
setStart(startTime, scale);
|
|
setStop(stopTime, scale);
|
|
}
|
|
|
|
void TimeBase::getSegment(TimeValue &startTime, TimeValue &stopTime, const TimeScale scale) const {
|
|
startTime = getStart(scale);
|
|
stopTime = getStop(scale);
|
|
}
|
|
|
|
TimeValue TimeBase::getDuration(const TimeScale scale) const {
|
|
TimeValue startTime, stopTime;
|
|
getSegment(startTime, stopTime, scale);
|
|
return stopTime - startTime;
|
|
}
|
|
|
|
void TimeBase::setMasterTimeBase(TimeBase *tb) {
|
|
_master = tb;
|
|
}
|
|
|
|
void TimeBase::updateTime() {
|
|
if (_master) {
|
|
_master->updateTime();
|
|
return;
|
|
}
|
|
|
|
if (_lastMillis == 0) {
|
|
_lastMillis = g_system->getMillis();
|
|
} else {
|
|
uint32 curTime = g_system->getMillis();
|
|
if (_lastMillis == curTime) // No change
|
|
return;
|
|
|
|
_time += Common::Rational(curTime - _lastMillis, 1000) * getRate();
|
|
_lastMillis = curTime;
|
|
}
|
|
}
|
|
|
|
void TimeBase::checkCallBacks() {
|
|
// Nothing to do if we're paused or not running
|
|
if (_paused || !isRunning())
|
|
return;
|
|
|
|
Common::Rational startTime = Common::Rational(_startTime, _startScale);
|
|
Common::Rational stopTime = Common::Rational(_stopTime, _stopScale);
|
|
|
|
// First step: update the times
|
|
updateTime();
|
|
|
|
// Clip time to the boundaries
|
|
if (_time >= stopTime)
|
|
_time = stopTime;
|
|
else if (_time <= startTime)
|
|
_time = startTime;
|
|
|
|
Common::Rational time = Common::Rational(getTime(), getScale());
|
|
|
|
// Check if we've triggered any callbacks
|
|
for (TimeBaseCallBack *runner = _callBackList; runner != 0; runner = runner->_nextCallBack) {
|
|
if (runner->_hasBeenTriggered)
|
|
continue;
|
|
|
|
if (runner->_type == kCallBackAtTime && runner->_trigger == kTriggerTimeFwd) {
|
|
if (getTime() >= (runner->_param2 * _preferredScale / runner->_param3) && getRate() > 0) {
|
|
uint param2 = runner->_param2, param3 = runner->_param3;
|
|
runner->callBack();
|
|
// HACK: Only stop future time forward callbacks if the parameters have not been changed
|
|
// This fixes striding callbacks. Since only striding callbacks do this kind of
|
|
// craziness, I'm not too worried about this.
|
|
runner->_hasBeenTriggered = (runner->_param2 == param2 && runner->_param3 == param3);
|
|
}
|
|
} else if (runner->_type == kCallBackAtExtremes) {
|
|
if (runner->_trigger == kTriggerAtStop) {
|
|
if (time == stopTime) {
|
|
runner->callBack();
|
|
runner->_hasBeenTriggered = true;
|
|
}
|
|
} else if (runner->_trigger == kTriggerAtStart) {
|
|
if (time == startTime) {
|
|
runner->callBack();
|
|
runner->_hasBeenTriggered = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (getFlags() & kLoopTimeBase) {
|
|
// Loop if necessary
|
|
if (getRate() < 0 && time == startTime)
|
|
setTime(_stopTime, _stopScale);
|
|
else if (getRate() > 0 && time == stopTime)
|
|
setTime(_startTime, _startScale);
|
|
} else {
|
|
// Stop at the end
|
|
if ((getRate() > 0 && time == stopTime) || (getRate() < 0 && time == startTime))
|
|
stop();
|
|
}
|
|
}
|
|
|
|
// Protected functions only called by TimeBaseCallBack.
|
|
|
|
void TimeBase::addCallBack(TimeBaseCallBack *callBack) {
|
|
callBack->_nextCallBack = _callBackList;
|
|
_callBackList = callBack;
|
|
}
|
|
|
|
void TimeBase::removeCallBack(TimeBaseCallBack *callBack) {
|
|
if (_callBackList == callBack) {
|
|
_callBackList = callBack->_nextCallBack;
|
|
} else {
|
|
TimeBaseCallBack *runner, *prevRunner;
|
|
|
|
for (runner = _callBackList->_nextCallBack, prevRunner = _callBackList; runner != callBack; prevRunner = runner, runner = runner->_nextCallBack)
|
|
;
|
|
|
|
prevRunner->_nextCallBack = runner->_nextCallBack;
|
|
}
|
|
|
|
callBack->_nextCallBack = 0;
|
|
}
|
|
|
|
void TimeBase::disposeAllCallBacks() {
|
|
TimeBaseCallBack *nextRunner;
|
|
|
|
for (TimeBaseCallBack *runner = _callBackList; runner != 0; runner = nextRunner) {
|
|
nextRunner = runner->_nextCallBack;
|
|
runner->disposeCallBack();
|
|
runner->_nextCallBack = 0;
|
|
}
|
|
|
|
_callBackList = 0;
|
|
}
|
|
|
|
TimeBaseCallBack::TimeBaseCallBack() {
|
|
_timeBase = 0;
|
|
_nextCallBack = 0;
|
|
_trigger = kTriggerNone;
|
|
_type = kCallBackNone;
|
|
_hasBeenTriggered = false;
|
|
}
|
|
|
|
TimeBaseCallBack::~TimeBaseCallBack() {
|
|
releaseCallBack();
|
|
}
|
|
|
|
void TimeBaseCallBack::initCallBack(TimeBase *tb, CallBackType type) {
|
|
releaseCallBack();
|
|
_timeBase = tb;
|
|
_timeBase->addCallBack(this);
|
|
_type = type;
|
|
}
|
|
|
|
void TimeBaseCallBack::releaseCallBack() {
|
|
if (_timeBase)
|
|
_timeBase->removeCallBack(this);
|
|
disposeCallBack();
|
|
}
|
|
|
|
void TimeBaseCallBack::disposeCallBack() {
|
|
_timeBase = 0;
|
|
_hasBeenTriggered = false;
|
|
}
|
|
|
|
void TimeBaseCallBack::scheduleCallBack(CallBackTrigger trigger, uint32 param2, uint32 param3) {
|
|
// TODO: Rename param2/param3?
|
|
_trigger = trigger;
|
|
_param2 = param2;
|
|
_param3 = param3;
|
|
_hasBeenTriggered = false;
|
|
}
|
|
|
|
void TimeBaseCallBack::cancelCallBack() {
|
|
_trigger = kTriggerNone;
|
|
_hasBeenTriggered = false;
|
|
}
|
|
|
|
IdlerTimeBase::IdlerTimeBase() {
|
|
_lastTime = 0xffffffff;
|
|
startIdling();
|
|
}
|
|
|
|
void IdlerTimeBase::useIdleTime() {
|
|
uint32 currentTime = getTime();
|
|
if (currentTime != _lastTime) {
|
|
_lastTime = currentTime;
|
|
timeChanged(_lastTime);
|
|
}
|
|
}
|
|
|
|
NotificationCallBack::NotificationCallBack() {
|
|
_callBackFlag = 0;
|
|
_notifier = 0;
|
|
}
|
|
|
|
void NotificationCallBack::callBack() {
|
|
if (_notifier)
|
|
_notifier->setNotificationFlags(_callBackFlag, _callBackFlag);
|
|
}
|
|
|
|
static const NotificationFlags kFuseExpiredFlag = 1;
|
|
|
|
Fuse::Fuse() : _fuseNotification(0, (NotificationManager *)((PegasusEngine *)g_engine)) {
|
|
_fuseNotification.notifyMe(this, kFuseExpiredFlag, kFuseExpiredFlag);
|
|
_fuseCallBack.setNotification(&_fuseNotification);
|
|
_fuseCallBack.initCallBack(&_fuseTimer, kCallBackAtExtremes);
|
|
_fuseCallBack.setCallBackFlag(kFuseExpiredFlag);
|
|
}
|
|
|
|
void Fuse::primeFuse(const TimeValue frequency, const TimeScale scale) {
|
|
stopFuse();
|
|
_fuseTimer.setScale(scale);
|
|
_fuseTimer.setSegment(0, frequency);
|
|
_fuseTimer.setTime(0);
|
|
}
|
|
|
|
void Fuse::lightFuse() {
|
|
if (!_fuseTimer.isRunning()) {
|
|
_fuseCallBack.scheduleCallBack(kTriggerAtStop, 0, 0);
|
|
_fuseTimer.start();
|
|
}
|
|
}
|
|
|
|
void Fuse::stopFuse() {
|
|
_fuseTimer.stop();
|
|
_fuseCallBack.cancelCallBack();
|
|
// Make sure the fuse has not triggered but not been caught yet...
|
|
_fuseNotification.setNotificationFlags(0, 0xffffffff);
|
|
}
|
|
|
|
void Fuse::advanceFuse(const TimeValue time) {
|
|
if (_fuseTimer.isRunning()) {
|
|
_fuseTimer.stop();
|
|
_fuseTimer.setTime(_fuseTimer.getTime() + time);
|
|
_fuseTimer.start();
|
|
}
|
|
}
|
|
|
|
TimeValue Fuse::getTimeRemaining() {
|
|
return _fuseTimer.getStop() - _fuseTimer.getTime();
|
|
}
|
|
|
|
void Fuse::receiveNotification(Notification *, const NotificationFlags) {
|
|
stopFuse();
|
|
invokeAction();
|
|
}
|
|
|
|
} // End of namespace Pegasus
|