AGI: Add heuristic to detect delay loops within scripts

And in that case poll events, delay for a few milliseconds and
update screen.
This somewhat worked before the graphics rewrite because of
a timer hack.
This one tries to detect actual inner loops.
Happens in at least Police Quest 1 when playing poker.
This commit is contained in:
Martin Kiewitz 2016-02-14 22:43:52 +01:00
parent dcbcbb2120
commit 7b75936f56
5 changed files with 48 additions and 0 deletions

View File

@ -401,6 +401,8 @@ AgiEngine::AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBas
setupOpcodes();
_game._curLogic = NULL;
_instructionCounter = 0;
resetGetVarSecondsHeuristic();
_lastSaveTime = 0;

View File

@ -862,6 +862,12 @@ public:
int testIfCode(int);
void executeAgiCommand(uint8, uint8 *);
private:
void resetGetVarSecondsHeuristic();
uint32 _instructionCounter; /**< counts every instruction, that got executed, can wrap around */
uint32 _getVarSecondsHeuristicLastInstructionCounter; /**< last time VM_VAR_SECONDS were read */
uint16 _getVarSecondsHeuristicCounter; /**< how many times heuristic was triggered */
public:
// Some submethods of testIfCode
void skipInstruction(byte op);
@ -955,6 +961,8 @@ private:
public:
void redrawScreen();
void getVarSecondsTrigger();
void inGameTimerReset(uint32 newPlayTime = 0);
void inGameTimerResetPassedCycles();
void inGameTimerPause();

View File

@ -142,6 +142,9 @@ void AgiEngine::interpretCycle() {
oldScore = getVar(VM_VAR_SCORE);
oldSound = getFlag(VM_FLAG_SOUND_ON);
// Reset script heuristic here
resetGetVarSecondsHeuristic();
_game.exitAllLogics = false;
while (runLogic(0) == 0 && !(shouldQuit() || _restartGame)) {
setVar(VM_VAR_WORD_NOT_FOUND, 0);

View File

@ -23,6 +23,7 @@
#include "common/config-manager.h"
#include "agi/agi.h"
#include "agi/graphics.h"
namespace Agi {
@ -61,6 +62,8 @@ void AgiEngine::setVar(int16 varNr, byte newValue) {
byte AgiEngine::getVar(int16 varNr) {
switch (varNr) {
case VM_VAR_SECONDS:
getVarSecondsTrigger();
// is supposed to fall through
case VM_VAR_MINUTES:
case VM_VAR_HOURS:
case VM_VAR_DAYS:
@ -135,6 +138,35 @@ void AgiEngine::setVolumeViaSystemSetting() {
_game.vars[VM_VAR_VOLUME] = internalVolume;
}
void AgiEngine::resetGetVarSecondsHeuristic() {
_getVarSecondsHeuristicLastInstructionCounter = 0;
_getVarSecondsHeuristicCounter = 0;
}
// Called, when the scripts read VM_VAR_SECONDS
void AgiEngine::getVarSecondsTrigger() {
uint32 counterDifference = _instructionCounter - _getVarSecondsHeuristicLastInstructionCounter;
if (counterDifference <= 3) {
// Seconds were read within 3 instructions
_getVarSecondsHeuristicCounter++;
if (_getVarSecondsHeuristicCounter > 20) {
// More than 20 times in a row? This really seems to be an inner loop waiting for seconds to change
// This happens in at least:
// Police Quest 1 - Poker game
// Wait a few milliseconds, get events and update screen
// We MUST NOT process AGI events in here
wait(10);
processScummVMEvents();
_gfx->updateScreen();
_getVarSecondsHeuristicCounter = 0;
}
}
_getVarSecondsHeuristicLastInstructionCounter = _instructionCounter;
}
// In-Game timer, used for timer VM Variables
void AgiEngine::inGameTimerReset(uint32 newPlayTime) {
_lastUsedPlayTimeInCycles = newPlayTime / 50;

View File

@ -2327,6 +2327,9 @@ int AgiEngine::runLogic(int16 logicNr) {
}
#endif
// Just a counter for every instruction, that got executed
_instructionCounter++;
_game.execStack.back().curIP = state->_curLogic->cIP;
char st[101];