diff options
Diffstat (limited to 'SrcShared/Hordes.cpp')
-rw-r--r-- | SrcShared/Hordes.cpp | 2437 |
1 files changed, 2437 insertions, 0 deletions
diff --git a/SrcShared/Hordes.cpp b/SrcShared/Hordes.cpp new file mode 100644 index 0000000..3e4178a --- /dev/null +++ b/SrcShared/Hordes.cpp @@ -0,0 +1,2437 @@ +/* -*- mode: C++; tab-width: 4 -*- */ +/* ===================================================================== *\ + Copyright (c) 1999-2001 Palm, Inc. or its subsidiaries. + All rights reserved. + + This file is part of the Palm OS Emulator. + + 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. +\* ===================================================================== */ + +#include "EmCommon.h" +#include "Hordes.h" // class Hordes + +#include "CGremlins.h" // Gremlins +#include "CGremlinsStubs.h" // StubAppGremlinsOff +#include "EmApplication.h" // ScheduleQuit +#include "EmEventPlayback.h" // SaveEvents, LoadEvents, Clear, RecordEvents +#include "EmMapFile.h" // EmMapFile::Write, etc. +#include "EmMinimize.h" // EmMinimize::IsDone +#include "EmPatchState.h" // EmPatchState::UIInitialized +#include "EmSession.h" // gSession, ScheduleResumeHordesFromFile +#include "EmStreamFile.h" // kCreateOrOpenForWrite +#include "ErrorHandling.h" // Errors::ThrowIfPalmError +#include "Logging.h" // LogStartNew, etc. +#include "Platform.h" // Platform::GetMilliseconds +#include "PreferenceMgr.h" // Preference, gEmuPrefs +#include "ROMStubs.h" // EvtWakeup +#include "SessionFile.h" // Chunk, EmStreamChunk +#include "Startup.h" // HordeQuitWhenDone +#include "StringConversions.h" // ToString, FromString; +#include "Strings.r.h" // kStr_CmdOpen, etc. +#include "SystemMgr.h" // sysGetROMVerMajor + +#include <math.h> // sqrt +#include <time.h> // time, localtime + +//////////////////////////////////////////////////////////////////////////////////////// +// HORDES CONSTANTS + +static const int MAXGREMLINS = 999; + + +//////////////////////////////////////////////////////////////////////////////////////// +// HORDES STATIC DATA + +static Gremlins gTheGremlin; +static int32 gGremlinStartNumber; +static int32 gGremlinStopNumber; +static int32 gSwitchDepth; +static int32 gMaxDepth; + int32 gGremlinSaveFrequency; +DatabaseInfoList gGremlinAppList; +static int32 gCurrentGremlin; +static int32 gCurrentDepth; +static bool gIsOn; +static uint32 gStartTime; +static uint32 gStopTime; +static EmGremlinThreadInfo gGremlinHaltedInError[MAXGREMLINS + 1]; +static EmDirRef gHomeForHordesFiles; + +static Bool gForceNewHordesDirectory; +static EmDirRef gGremlinDir; + +Bool gWarningHappened; +Bool gErrorHappened; + + +//////////////////////////////////////////////////////////////////////////////////////// +// HORDES METHODS + +/*********************************************************************** + * + * FUNCTION: Hordes::Initialize + * + * DESCRIPTION: Standard initialization function. Responsible for + * initializing this sub-system when a new session is + * created. Will be followed by at least one call to + * Reset or Load. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void Hordes::Initialize (void) +{ + gTheGremlin.Reset (); +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::Reset + * + * DESCRIPTION: Standard reset function. Sets the sub-system to a + * default state. This occurs not only on a Reset (as + * from the menu item), but also when the sub-system + * is first initialized (Reset is called after Initialize) + * as well as when the system is re-loaded from an + * insufficient session file. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void Hordes::Reset (void) +{ + EmDlg::GremlinControlClose (); + Hordes::Stop (); + gTheGremlin.Reset (); +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::Save + * + * DESCRIPTION: Standard save function. Saves any sub-system state to + * the given session file. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void Hordes::Save (SessionFile& f) +{ + gTheGremlin.Save (f); +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::Load + * + * DESCRIPTION: Standard load function. Loads any sub-system state + * from the given session file. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void Hordes::Load (SessionFile& f) +{ + Bool fHordesOn = gTheGremlin.Load (f); + Hordes::TurnOn (fHordesOn); +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::Dispose + * + * DESCRIPTION: Standard dispose function. Completely release any + * resources acquired or allocated in Initialize and/or + * Load. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void Hordes::Dispose (void) +{ + gTheGremlin.Reset (); +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::New + * + * DESCRIPTION: Starts a new Horde of Gremlins + * + * PARAMETERS: HordeInfo& info - Horde initialization info + * + * RETURNED: none + * + ***********************************************************************/ + +void +Hordes::New(const HordeInfo& info) +{ + gGremlinStartNumber = min(info.fStartNumber, info.fStopNumber); + gGremlinStopNumber = max(info.fStartNumber, info.fStopNumber); + + if (info.fSwitchDepth == -1 || gGremlinStartNumber == gGremlinStopNumber) + gSwitchDepth = info.fMaxDepth; + + else + gSwitchDepth = info.fSwitchDepth; + + gMaxDepth = info.fMaxDepth; + gGremlinSaveFrequency = info.fSaveFrequency; + gGremlinAppList = info.fAppList; + gCurrentDepth = 0; + gCurrentGremlin = gGremlinStartNumber; + + if (gSwitchDepth == 0) + gSwitchDepth = -1; + + if (gMaxDepth == 0) + gMaxDepth = -1; + + for (int counter = 0; counter <= MAXGREMLINS; counter++) + { + gGremlinHaltedInError[counter].fHalted = false; + gGremlinHaltedInError[counter].fErrorEvent = 0; + gGremlinHaltedInError[counter].fMessageID = -1; + } + + GremlinInfo gremInfo; + + gremInfo.fNumber = gCurrentGremlin; + gremInfo.fSaveFrequency = gGremlinSaveFrequency; + gremInfo.fSteps = (gMaxDepth == -1) ? gSwitchDepth : min (gSwitchDepth, gMaxDepth); + gremInfo.fAppList = gGremlinAppList; + gremInfo.fFinal = gMaxDepth; + + gStartTime = Platform::GetMilliseconds (); + + gTheGremlin.New (gremInfo); + + EmDlg::GremlinControlOpen (); + + Hordes::UseNewAutoSaveDirectory (); + + EmEventPlayback::Clear (); + + // When we save our root state, we want it to be saved with all + // the correct GremlinInfo (per the 2.0 file format) but with + // Gremlins turned OFF. + + Hordes::TurnOn (false); + + Hordes::SaveRootState (); + + Hordes::TurnOn (true); + + Hordes::StartLog (); + + LogAppendMsg ("New Gremlin #%ld started anew to %ld events", + gremInfo.fNumber, gremInfo.fSteps); + + LogDump (); + + gWarningHappened = false; + gErrorHappened = false; +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::NewGremlin + * + * DESCRIPTION: Starts a new Horde of just one Gremlin -- + * "classic Gremlins" + * + * PARAMETERS: GremlinInfo& info - Gremlin initialization info + * + * RETURNED: none + * + ***********************************************************************/ + +void +Hordes::NewGremlin (const GremlinInfo &info) +{ + HordeInfo newHorde; + + newHorde.fStartNumber = info.fNumber; + newHorde.fStopNumber = info.fNumber; + newHorde.fSwitchDepth = info.fSteps; + newHorde.fMaxDepth = info.fSteps; + newHorde.fSaveFrequency = info.fSaveFrequency; + newHorde.fAppList = info.fAppList; + + Hordes::New (newHorde); +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::Status + * + * DESCRIPTION: Returns several pieces of status information about the + * currently running Gremlin in the Horde. + * + * PARAMETERS: currentNumber - returns the current Gremlin number. + * currentStep - returns the current event number of the + * currently running Gremlin + * currentUntil - returns the current upper event bound of + * currently running Gremlin + * + * RETURNED: none + * + ***********************************************************************/ + +void +Hordes::Status (unsigned short *currentNumber, unsigned long *currentStep, + unsigned long *currentUntil) +{ + gTheGremlin.Status (currentNumber, currentStep, currentUntil); +} + + +string +Hordes::GremlinsFlagsToString (void) +{ + string output; + + for (int ii = 0; ii < MAXGREMLINS; ++ii) + { + gGremlinHaltedInError[ii].fHalted ? output += "1" : output += "0"; + } + + return output; +} + +void +Hordes::GremlinsFlagsFromString(string& inFlags) +{ + for (int ii = 0; ii < MAXGREMLINS; ++ii) + { + gGremlinHaltedInError[ii].fHalted = (inFlags.c_str()[ii] == '1'); + } +} + +void +Hordes::SaveSearchProgress() +{ + StringStringMap searchProgress; + + searchProgress["gGremlinStartNumber"] = ::ToString (gGremlinStartNumber); + searchProgress["gGremlinStopNumber"] = ::ToString (gGremlinStopNumber); + searchProgress["gSwitchDepth"] = ::ToString (gSwitchDepth); + searchProgress["gMaxDepth"] = ::ToString (gMaxDepth); + searchProgress["gGremlinSaveFrequency"] = ::ToString (gGremlinSaveFrequency); + searchProgress["gCurrentGremlin"] = ::ToString (gCurrentGremlin); + searchProgress["gCurrentDepth"] = ::ToString (gCurrentDepth); + searchProgress["gGremlinHaltedInError"] = Hordes::GremlinsFlagsToString (); + searchProgress["gStartTime"] = ::ToString (gStartTime); + searchProgress["gStopTime"] = ::ToString (gStopTime); + + EmFileRef searchFile = Hordes::SuggestFileRef (kHordeProgressFile); + + EmMapFile::Write (searchFile, searchProgress); +} + + +void +Hordes::ResumeSearchProgress (const EmFileRef& f) +{ + StringStringMap searchProgress; + + EmMapFile::Read (f, searchProgress); + + ::FromString (searchProgress["gGremlinStartNumber"], gGremlinStartNumber); + ::FromString (searchProgress["gGremlinStopNumber"], gGremlinStopNumber); + ::FromString (searchProgress["gSwitchDepth"], gSwitchDepth); + ::FromString (searchProgress["gMaxDepth"], gMaxDepth); + ::FromString (searchProgress["gGremlinSaveFrequency"], gGremlinSaveFrequency); + ::FromString (searchProgress["gCurrentGremlin"], gCurrentGremlin); + ::FromString (searchProgress["gCurrentDepth"], gCurrentDepth); + + Hordes::GremlinsFlagsFromString (searchProgress["gGremlinHaltedInError"]); + + // Get, then patch up start and stop times. + + ::FromString (searchProgress["gStartTime"], gStartTime); + ::FromString (searchProgress["gStopTime"], gStopTime); + + uint32 delta = gStopTime - gStartTime; + gStopTime = Platform::GetMilliseconds (); + gStartTime = gStopTime - delta; + +// gSession->ScheduleResumeHordesFromFile (); +} + +Bool +Hordes::IsOn (void) +{ + return gIsOn; +} + +Bool +Hordes::InSingleGremlinMode (void) +{ + return gGremlinStartNumber == gGremlinStopNumber; +} + + +Bool +Hordes::QuitWhenDone (void) +{ + if (Startup::HordeQuitWhenDone ()) + return true; + + return false; +} + + +Bool +Hordes::CanNew (void) +{ + return !EmMinimize::IsOn () && EmPatchState::UIInitialized (); +} + + +Bool +Hordes::CanSuspend (void) +{ + return !EmMinimize::IsOn (); +} + + +Bool +Hordes::CanStep (void) +{ + return (gTheGremlin.IsInitialized () && !gIsOn && !EmMinimize::IsOn ()); +} + + +Bool +Hordes::CanResume (void) +{ + return (gTheGremlin.IsInitialized () && !gIsOn && !EmMinimize::IsOn ()); +} + + +Bool +Hordes::CanStop (void) +{ + return (gIsOn && !EmMinimize::IsOn ()); +} + + +int32 +Hordes::GremlinNumber (void) +{ + return gCurrentGremlin; +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::TurnOn + * + * DESCRIPTION: Turns Hordes on or off. + * + * PARAMETERS: fHordesOn - specifies if Hordes should be on or off. + * + * RETURNED: none + * + ***********************************************************************/ + +void +Hordes::TurnOn (Bool hordesOn) +{ + gIsOn = (hordesOn != false); + EmEventPlayback::RecordEvents (gIsOn); +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::EventCounter + * + * DESCRIPTION: Returns the current event count of the currently running + * Gremlin + * + * PARAMETERS: none + * + * RETURNED: event count + * + ***********************************************************************/ + +int32 +Hordes::EventCounter (void) +{ + unsigned short number; + unsigned long step; + unsigned long until; + + Hordes::Status (&number, &step, &until); + + return step; +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::EventLimit + * + * DESCRIPTION: Returns the current event limit of the currently running + * Gremlin + * + * PARAMETERS: none + * + * RETURNED: event limit + * + ***********************************************************************/ + +int32 +Hordes::EventLimit(void) +{ + unsigned short number; + unsigned long step; + unsigned long until; + + Hordes::Status (&number, &step, &until); + + return until; +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::EndHordes + * + * DESCRIPTION: Ends Hordes, giving back control to user. + * + * PARAMETERS: none + * + * RETURNED: none + * + ***********************************************************************/ + +void +Hordes::EndHordes (void) +{ + // In an odd twist, logging doesn't work if Hordes is supposedly + // turned off. So let's spoof that it's on; it will be turned off -- + // again -- below, when Hordes::Stop() is called. + + Hordes::TurnOn (true); + + if (!Hordes::InSingleGremlinMode ()) + { + LogAppendMsg ("************* Gremlin Horde ended at Gremlin #%ld\n", gGremlinStopNumber); + } + + // It's time to print out some basic info: + // ROM version + // ROM file name + // Device name + // RAM size + + UInt32 romVersionData; + ::FtrGet (sysFileCSystem, sysFtrNumROMVersion, &romVersionData); + + UInt32 romVersionMajor = sysGetROMVerMajor (romVersionData); + UInt32 romVersionMinor = sysGetROMVerMinor (romVersionData); + + Preference<Configuration> pref (kPrefKeyLastConfiguration); + Preference<EmFileRef> pref2 (kPrefKeyLastPSF); + + Configuration cfg = *pref; + EmDevice device = cfg.fDevice; + string deviceStr = device.GetIDString (); + RAMSizeType ramSize = cfg.fRAMSize; + EmFileRef romFile = cfg.fROMFile; + string romFileStr = romFile.GetFullPath (); + EmFileRef sessionFile = *pref2; + string sessionFileStr = sessionFile.GetFullPath (); + + if (sessionFileStr.empty ()) + { + sessionFileStr = "<Not selected>"; + } + + LogAppendMsg ("************* Device Info:"); + LogAppendMsg ("ROM version: %d.%d", romVersionMajor, romVersionMinor); + LogAppendMsg ("ROM file name: %s", (char *) romFileStr.c_str ()); + LogAppendMsg ("Session file: %s", (char *) sessionFileStr.c_str ()); + LogAppendMsg ("Device name: %s", (char *) deviceStr.c_str ()); + LogAppendMsg ("RAM size: %d KB\n", (long) ramSize); + + // Let's come up with some statistics from our new field in + // gGremlinHaltedInError. + + int32 min, max, avg, stdDev, smallErrorIndex; + Hordes::ComputeStatistics (min, max, avg, stdDev, smallErrorIndex); + + LogAppendMsg ("************* Error Occurrence Statistics:"); + LogAppendMsg (""); + + Hordes::GremlinReport (); + + // check for the sentinel value + + if (smallErrorIndex != 0x7FFFFFFF) + { + LogAppendMsg (""); + LogAppendMsg ("Minimum Event: %d", min); + LogAppendMsg ("Maximum Event: %d", max); + LogAppendMsg ("Average Event: %d", avg); + LogAppendMsg ("Standard Deviation: %d", stdDev); + LogAppendMsg ("Overall Shortest Gremlin: #%d\n", smallErrorIndex); + } + else + { + LogAppendMsg ("No Gremlins found errors.\n"); + } + + LogDump (); + + Hordes::TurnOn (false); + + LogClear(); + EmEventPlayback::Clear (); + + if (!Hordes::InSingleGremlinMode ()) + { + EmDlg::GremlinControlClose (); + + EmAssert (gSession); + gSession->ScheduleLoadRootState (); + } + + if (Hordes::QuitWhenDone ()) + { + EmAssert (gApplication); + gApplication->ScheduleQuit (); + } + else + { + gWarningHappened = false; + gErrorHappened = false; + } +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::ProposeNextGremlin + * + * DESCRIPTION: Proposes the NEXT Gremlin # and corresponding search + * depth, FROM the state of the Hordes run specified by the + * input paramaters + * + * PARAMETERS: outNextGremlin - passes back suggested next Gremlin + * outNextDepth - passes back next depth + * inFromGremlin - "current" Gremlin + * inFromGremlin - "current" depth + * + * RETURNED: none + * + ***********************************************************************/ + +void +Hordes::ProposeNextGremlin (long& outNextGremlin, long& outNextDepth, + long inFromGremlin, long inFromDepth) +{ + outNextGremlin = inFromGremlin + 1; + outNextDepth = inFromDepth; + + if (outNextGremlin == gGremlinStopNumber + 1) + { + outNextGremlin = gGremlinStartNumber; + + if (outNextDepth >= 0) + ++outNextDepth; + } +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::StartGremlinFromLoadedRootState + * + * DESCRIPTION: After the CPU loads the root state during an off-cycle, + * it calls this to indicate that Hordes is meant to start + * the current Gremlin, and that the Emulator state is ready + * for this. + * + * PARAMETERS: none + * + * RETURNED: none + * + ***********************************************************************/ + +void +Hordes::StartGremlinFromLoadedRootState (void) +{ + GremlinInfo gremInfo; + + gremInfo.fNumber = gCurrentGremlin; + gremInfo.fSaveFrequency = gGremlinSaveFrequency; + gremInfo.fSteps = ((gMaxDepth == -1) ? gSwitchDepth : min(gSwitchDepth, gMaxDepth)); + gremInfo.fAppList = gGremlinAppList; + gremInfo.fFinal = gMaxDepth; + + gTheGremlin.New (gremInfo); + + LogAppendMsg ("New Gremlin #%ld started from root state to %ld events", + gCurrentGremlin, gremInfo.fSteps); +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::StartGremlinFromLoadedSuspendedState + * + * DESCRIPTION: After the CPU loads the suspended state during an off-cycle, + * it calls this to indicate that Hordes is meant to resume + * the current Gremlin, and that the Emulator state is ready + * for this. + * + * PARAMETERS: none + * + * RETURNED: none + * + ***********************************************************************/ + +void +Hordes::StartGremlinFromLoadedSuspendedState (void) +{ + // We reset the Gremlin to go until the next occurence of the + // depth-bound, or until gMaxDepth, whichever occurs first. + + long newUntil = gSwitchDepth * (gCurrentDepth + 1); + + if (gMaxDepth != -1) + { + newUntil = min (newUntil, gMaxDepth); + } + + gTheGremlin.SetUntil (newUntil); + + LogAppendMsg("Resuming Gremlin #%ld to #%ld events", + gCurrentGremlin, newUntil); + + Hordes::TurnOn(true); +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::SetGremlinStatePathFromControlFile + * + * DESCRIPTION: Given the file reference to a control file (actually *any* + * file in the Gremlins state path), set the Gremlins state + * path to the directory that contains this file. + * + * PARAMETERS: controlFile - reference to the rootStateFile. + * + * RETURNED: nothing + * + ***********************************************************************/ + +void +Hordes::SetGremlinStatePathFromControlFile (EmFileRef& controlFile) +{ + gGremlinDir = controlFile.GetParent (); + gForceNewHordesDirectory = false; +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::NextGremlin + * + * DESCRIPTION: Selects and runs the next Gremlin in the Horde, if there + * are unfinished Gremlins left. Otherwise, just loads + * the pre-Horde state and stops. + * + * PARAMETERS: none + * + * RETURNED: none + * + ***********************************************************************/ + +void +Hordes::NextGremlin (void) +{ + Hordes::Stop (); + + Hordes::SaveSearchProgress (); + + // Find the next Gremlin to run. + + long nextGremlin, nextDepth; + + // Keep looking until we find a Gremlin in the range which has + // not halted in error. + + Hordes::ProposeNextGremlin (nextGremlin, nextDepth, gCurrentGremlin, gCurrentDepth); + + while (gGremlinHaltedInError[nextGremlin].fHalted) + { + // All Gremlins halted in error when we are back at the current + // Gremlin at the next depth. (We looped around without finding + // the next Gremlin that didn't halt in error). + + if (nextGremlin == gCurrentGremlin && nextDepth >= gCurrentDepth + 1) + { + Hordes::EndHordes (); + return; + } + + Hordes::ProposeNextGremlin (nextGremlin, nextDepth, nextGremlin, nextDepth); + } + + // Update our current location in the Gremlin search tree. + + gCurrentGremlin = nextGremlin; + gCurrentDepth = nextDepth; + + // All the Gremlins have reached gMaxDepth when the depth exceeds the + // depth necessary to reach gMaxDepth. Special case for + // gMaxDepth = forever. + + if ( gMaxDepth != -1 && + ( (gCurrentDepth > gMaxDepth / gSwitchDepth) || + ( (gCurrentDepth == gMaxDepth / gSwitchDepth) && (gMaxDepth % gSwitchDepth == 0) ) ) ) + { + Hordes::EndHordes(); + return; + } + + // If the current depth is 0, we start at the root state. + + if (gCurrentDepth == 0) + { + EmAssert (gSession); + gSession->ScheduleNextGremlinFromRootState (); + } + + // Otherwise, we load the suspended state, which is where we begin to + // resume the Gremlin + + else + { + EmAssert (gSession); + gSession->ScheduleNextGremlinFromSuspendedState (); + } +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::ErrorEncountered + * + * DESCRIPTION: Called when an error condition has been encountered. + * This function saves the current state and starts in + * motion the machinery to start the next Gremlin in the + * Horde. + * + * PARAMETERS: none + * + * RETURNED: none + * + ***********************************************************************/ + +void +Hordes::ErrorEncountered (void) +{ + int32 errorGremlin = Hordes::GremlinNumber (); + int32 errorEvent = Hordes::EventCounter () - 1; + + Hordes::AutoSaveState (); + + LogAppendMsg ("=== ERROR: Gremlin #%ld terminated in error at event #%ld\n", + errorGremlin, errorEvent); + + LogDump (); + + // This is a fatal error; stop the execution of this Gremlin. + + gGremlinHaltedInError[errorGremlin].fHalted = true; + + // Save the events, now that it's terminated with an error event. + + Hordes::SaveEvents (); + + // Move to the next Gremlin. + +// if (!Hordes::InSingleGremlinMode ()) + { + Hordes::NextGremlin (); + } +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::RecordErrorStats + * + * DESCRIPTION: Called when an error message needs to be displayed, + * either as a result of a hardware exception, an error + * condition detected by Poser, or the Palm application + * calling SysFatalAlert. If appropriate, log the error + * message. + * + * PARAMETERS: messageID - ID indicating what error occurred. We pass + * that into here so that we can keep stats on the kinds + * of errors generated. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void +Hordes::RecordErrorStats (StrCode messageID) +{ + if (Hordes::IsOn ()) + { + // Update our gGremlinHaltedInError info if it was passed, and if + // we haven't already assigned an error to this Gremlin. (This is + // so that the first error, of possibly many if we are continuing + // on errors, is the one that shows up in the statistics.) + + int32 errorGremlin = Hordes::GremlinNumber(); + int32 errorEvent = Hordes::EventCounter () - 1; + + // Only update this if we haven't already logged an error event for the first + // error + + Bool firstErrForGremlin = gGremlinHaltedInError[errorGremlin].fMessageID == -1; + + if (firstErrForGremlin) + { + gGremlinHaltedInError[errorGremlin].fErrorEvent = errorEvent; + + // Now update the message id if a valid one was passed + + if (messageID != -1) + { + gGremlinHaltedInError[errorGremlin].fMessageID = (long) messageID; + } + } + + // Tell Minimization that a warning or error occurred. + // + // Do we ever get here? RecordErrorStats is called from Errors::DoDialog. + // Errors::DoDialog is called from Errors::HandleDialog, which calls + // EmMinimize::ErrorOccurred and returns before calling Errors::DoDialog + // if minimization is turned on. + + if (EmMinimize::IsOn ()) + { + EmAssert (false); // See if we get here. + + LogAppendMsg ("Calling EmMinimize::ErrorOccurred from Hordes::RecordErrorStats"); + EmMinimize::ErrorOccurred (); + } + } +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::StopEventReached + * + * DESCRIPTION: Message to Hordes indicating that a Gremlin has + * completed its last event. Saves a suspended state if + * we intend to resume this Gremlin in the future. + * + * PARAMETERS: none + * + * RETURNED: none + * + ***********************************************************************/ + +void +Hordes::StopEventReached() +{ + int32 gremlinNumber = Hordes::GremlinNumber (); + int32 stopEventNumber = Hordes::EventLimit (); + + LogAppendMsg ("Gremlin #%ld finished successfully to event #%ld", + gremlinNumber, stopEventNumber); + + if (stopEventNumber == gMaxDepth) + { +// LogAppendMsg ("********************************************************************************"); + LogAppendMsg ("************* Gremlin #%ld successfully completed", gremlinNumber); +// LogAppendMsg ("********************************************************************************"); + } + + LogDump (); + + // Save the events of the successful run. + + Hordes::SaveEvents (); + + // Move to the next Gremlin. + + Hordes::NextGremlin (); +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::Suspend + * + * DESCRIPTION: Suspends the currently running Gremlin. + * + * PARAMETERS: none + * + * RETURNED: none + * + ***********************************************************************/ + +void +Hordes::Suspend() +{ +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::Step + * + * DESCRIPTION: "Steps" the currently running Gremlin. + * + * PARAMETERS: none + * + * RETURNED: none + * + ***********************************************************************/ + +void +Hordes::Step() +{ + if (!gIsOn) + { + Hordes::TurnOn (true); + + // Spoof info to look as if this was a single Gremlin run. This way, + // stepping will work; if this is not done, then the next Gremlin is + // launched, just as if a switching barrier was reached. + + gCurrentDepth = gMaxDepth; + gGremlinStartNumber = gGremlinStopNumber = gCurrentGremlin; + + gTheGremlin.Step (); + + // Make sure the app's awake. Normally, we post events on a patch to + // SysEvGroupWait. However, if the Palm device is already waiting, + // then that trap will never get called. By calling EvtWakeup now, + // we'll wake up the Palm device from its nap. + + Errors::ThrowIfPalmError (EvtWakeup ()); + } +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::Resume + * + * DESCRIPTION: Resumes the currently suspended Gremlin. + * + * PARAMETERS: none + * + * RETURNED: none + * + ***********************************************************************/ + +void +Hordes::Resume() +{ + if (!Hordes::IsOn ()) + { + Hordes::TurnOn (true); + + gStartTime = Platform::GetMilliseconds () - (gStopTime - gStartTime); + + gTheGremlin.RestoreFinalUntil (); + + gTheGremlin.Resume (); + } +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::Stop + * + * DESCRIPTION: Suspends the currently running Gremlin. + * + * PARAMETERS: none + * + * RETURNED: none + * + ***********************************************************************/ + +void +Hordes::Stop (void) +{ + gStopTime = Platform::GetMilliseconds (); + + StubAppGremlinsOff (); + + gTheGremlin.Stop (); +} + + +/***************************************************************************** + * + * FUNCTION: Hordes::SuggestFileName + * + * DESCRIPTION: This function is responsible for deciding about the names of + * files that are created during a horde run. The file names + * are assigned based on the requested file category. Some + * file categories use gremlins data (event number, gremlin + * number) to construct a unique file name. The following + * categories are used by the "Horde" class: + * + * kHordeProgressFile + * kHordeRootFile + * kHordeSuspendFile + * kHordeAutoCurrentFile + * + * kHordeSuspendFile - last file in a gremlin thread + * + * PARAMETERS: file category + * + * RETURNED: file name or an empty string when the input category is + * incorrect + * + *****************************************************************************/ + +string Hordes::SuggestFileName (HordeFileType category, uint32 num) +{ + static const char kStrSearchProgressFile[] = "Gremlin_Search_Progress.dat"; + static const char kStrRootStateFile[] = "Gremlin_Root_State.psf"; + static const char kStrSuspendedStateFile[] = "Gremlin_%03ld_Suspended.psf"; + static const char kStrAutoSaveFile[] = "Gremlin_%03ld_Event_%08ld.psf"; + static const char kStrEventFile[] = "Gremlin_%03ld_Events.pev"; + static const char kStrMinimalEventFile [] = "Gremlin_%03ld_Interim_Event_File_%08ld.pev"; + + char fileName[64]; + + int32 gremlinNumber; + int32 eventCounter; + uint32 time; + + switch (category) + { + case kHordeProgressFile: + + strcpy (fileName, kStrSearchProgressFile); + break; + + case kHordeRootFile: + + strcpy (fileName, kStrRootStateFile); + break; + + case kHordeSuspendFile: + + gremlinNumber = Hordes::GremlinNumber (); + sprintf (fileName, kStrSuspendedStateFile, gremlinNumber); + break; + + case kHordeAutoCurrentFile: + + gremlinNumber = Hordes::GremlinNumber (); + eventCounter = Hordes::EventCounter (); + + if (gGremlinSaveFrequency == 0) + { + sprintf (fileName, kStrAutoSaveFile, gremlinNumber, eventCounter); + } + else + { + eventCounter = (eventCounter / gGremlinSaveFrequency) * gGremlinSaveFrequency; + sprintf (fileName, kStrAutoSaveFile, gremlinNumber, eventCounter); + } + break; + + case kHordeEventFile: + + gremlinNumber = Hordes::GremlinNumber (); + sprintf (fileName, kStrEventFile, gremlinNumber); + break; + + case kHordeMinimalEventFile: + + gremlinNumber = num; + time = Platform::GetMilliseconds (); + sprintf (fileName, kStrMinimalEventFile, gremlinNumber, time); + break; + + default: + + *fileName = '\0'; + break; + }; + + return string (fileName); +} + + +/***************************************************************************** + * + * FUNCTION: Hordes::SuggestFileRef + * + * DESCRIPTION: This function is responsible for deciding about the names of + * files that are created during a horde run. The file names + * are assigned based on the requested file category. Some + * file categories use gremlins data (event number, gremlin + * number) to construct a unique file name. The following + * categories are used by the "Horde" class: + * + * kHordeProgressFile + * kHordeRootFile + * kHordeSuspendFile + * kHordeAutoCurrentFile + * + * kHordeSuspendFile - last file in a gremlin thread + * + * PARAMETERS: file category + * + * RETURNED: file ref + * + *****************************************************************************/ + +EmFileRef Hordes::SuggestFileRef (HordeFileType category, uint32 num) +{ + EmFileRef fileRef ( + Hordes::GetGremlinDirectory (), + Hordes::SuggestFileName (category, num)); + + return fileRef; +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::PostLoad + * + * DESCRIPTION: Initializes a load that has taken place outside the + * control of the Hordes subsystem. For example, if the user + * has opened a session file manually. This is to set the + * Hordes state to play the Gremlin in the file as a "horde + * of one." + * + * PARAMETERS: f - SessionFile to load from + * + * RETURNED: none + * + ***********************************************************************/ + +void +Hordes::PostLoad (void) +{ + // We can't just call NewGremlin with the GremlinInfo because + // gTheGremlin.Load() has already restored the state of the Gremlin, + // so we should not call gTheGremlin.New() on it. + + Preference<GremlinInfo> pref (kPrefKeyGremlinInfo); + GremlinInfo info = *pref; + + gGremlinStartNumber = info.fNumber; + gGremlinStopNumber = info.fNumber; + gCurrentGremlin = info.fNumber; + gSwitchDepth = info.fSteps; + gMaxDepth = info.fSteps; + gGremlinSaveFrequency = info.fSaveFrequency; + gGremlinAppList = info.fAppList; + gCurrentDepth = 0; + + gStartTime = gTheGremlin.GetStartTime (); + gStopTime = gTheGremlin.GetStopTime (); + + if (Hordes::IsOn()) + { + Hordes::UseNewAutoSaveDirectory (); + + EmAssert (gSession); + gSession->ScheduleSaveRootState (); + } +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::PostFakeEvent + * + * DESCRIPTION: Posts a fake event through the currently running Gremlin. + * + * PARAMETERS: none + * + * RETURNED: TRUE if a key or point was enqueued, FALSE otherwise. + * + ***********************************************************************/ + +Bool +Hordes::PostFakeEvent (void) +{ + // check to see if the Gremlin has produced its max # of "events." + + if (Hordes::EventLimit() > 0 && Hordes::EventCounter () > Hordes::EventLimit ()) + { + Hordes::StopEventReached (); + return false; + } + + Bool result = gTheGremlin.GetFakeEvent (); + + Hordes::BumpCounter (); + + return result; +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::PostFakePenEvent + * + * DESCRIPTION: Posts a phony pen movement to through the currently + * running Gremlin + * + * PARAMETERS: none + * + * RETURNED: none + * + ***********************************************************************/ + +void +Hordes::PostFakePenEvent (void) +{ + Hordes::BumpCounter (); + gTheGremlin.GetPenMovement (); +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::SendCharsToType + * + * DESCRIPTION: Send a char to the Emulator if any are pending for the + * currently running Gremlin + * + * PARAMETERS: none + * + * RETURNED: none + * + ***********************************************************************/ + +Bool +Hordes::SendCharsToType (void) +{ + return gTheGremlin.SendCharsToType (); +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::ElapsedMilliseconds + * + * DESCRIPTION: Returns the elapsed time of the Horde + * + * PARAMETERS: none + * + * RETURNED: none + * + ***********************************************************************/ + +uint32 +Hordes::ElapsedMilliseconds (void) +{ + if (gIsOn) + return Platform::GetMilliseconds () - gStartTime; + + return gStopTime - gStartTime; +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::BumpCounter + * + * DESCRIPTION: Bumps event counter of the currently running Gremlin + * + * PARAMETERS: none + * + * RETURNED: none + * + ***********************************************************************/ + +void +Hordes::BumpCounter (void) +{ + gTheGremlin.BumpCounter (); + + if (gGremlinSaveFrequency != 0 && + (Hordes::EventCounter () % gGremlinSaveFrequency) == 0) + { + EmAssert (gSession); + gSession->ScheduleAutoSaveState (); + } + + if (Hordes::EventLimit () > 0 && + Hordes::EventLimit () == Hordes::EventCounter ()) + { + EmAssert (gSession); + gSession->ScheduleSaveSuspendedState (); + } +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::CanSwitchToApp + * + * DESCRIPTION: Returns whether the Horde can switch to the given Palm + * app + * + * PARAMETERS: cardNo \ Palm application info + * dbID / + * + * RETURNED: TRUE if the designated Palm app can be run by the Horde + * FALSE otherwise + * + ***********************************************************************/ + +Bool +Hordes::CanSwitchToApp (UInt16 cardNo, LocalID dbID) +{ + if (gGremlinAppList.size () == 0) + return true; + + DatabaseInfoList appList = Hordes::GetAppList (); + DatabaseInfoList::iterator iter = appList.begin (); + + while (iter != appList.end ()) + { + DatabaseInfo& dbInfo = *iter++; + + if (dbInfo.cardNo == cardNo && dbInfo.dbID == dbID) + return true; + } + + return false; +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::SetGremlinsHome + * + * DESCRIPTION: Indicates the directory in which the user would like + * his Horde files to collect. + * + * PARAMETERS: gremlinsHome - the name of the directory + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void +Hordes::SetGremlinsHome (const EmDirRef& gremlinsHome) +{ + gHomeForHordesFiles = gremlinsHome; +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::SetGremlinsHomeToDefault + * + * DESCRIPTION: Indicates that the user would like his Horde files to + * collect in the default location. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void +Hordes::SetGremlinsHomeToDefault (void) +{ + gHomeForHordesFiles = EmDirRef (); +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::GetGremlinsHome + * + * DESCRIPTION: Returns the directory that the user would like to house + * his Horde state files. + * + * PARAMETERS: None. + * + * RETURNED: TRUE if gHomeForHordesFiles is defined; + * FALSE otherwise (use default). + * + ***********************************************************************/ + +Bool +Hordes::GetGremlinsHome (EmDirRef& outPath) +{ + // If we don't have anything, default to Poser home. + + outPath = gHomeForHordesFiles; + + // Try to create the path if it doesn't exist. + + if (!gHomeForHordesFiles.Exists ()) + { + gHomeForHordesFiles.Create (); + } + + // Return whether or not we succeeded. + + return gHomeForHordesFiles.Exists (); +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::AutoSaveState + * + * DESCRIPTION: Creates a file reference to where the auto-saved state + * should be saved. Then calls a platform-specific + * routine to do the actual saving. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void +Hordes::AutoSaveState (void) +{ + EmFileRef fileRef = Hordes::SuggestFileRef (kHordeAutoCurrentFile); + + EmAssert (gSession); + gSession->Save (fileRef, false); +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::SaveRootState + * + * DESCRIPTION: Creates a file reference to where the state + * should be saved. Then calls a platform-specific + * routine to do the actual saving. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void +Hordes::SaveRootState (void) +{ + EmFileRef fileRef = Hordes::SuggestFileRef (kHordeRootFile); + + Bool hordesWasOn = Hordes::IsOn (); + + if (hordesWasOn != false) + Hordes::TurnOn (false); + + EmAssert (gSession); + gSession->Save (fileRef, false); + + Hordes::TurnOn (hordesWasOn); +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::LoadState + * + * DESCRIPTION: Does the work of loading a state while Hordes is running. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +ErrCode +Hordes::LoadState (const EmFileRef& ref) +{ + ErrCode returnedErrCode = errNone; + + try + { + EmAssert (gSession); + gSession->Load (ref); + } + catch (ErrCode errCode) + { + Hordes::TurnOn (false); + + Errors::SetParameter ("%filename", ref.GetName ()); + Errors::ReportIfError (kStr_CmdOpen, errCode, 0, true); + + returnedErrCode = errCode; + } + + return returnedErrCode; +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::LoadRootState + * + * DESCRIPTION: Creates a file reference to where the root state + * should be loaded. Then calls a + * routine to do the actual loading. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +ErrCode +Hordes::LoadRootState (void) +{ + EmFileRef fileRef = Hordes::SuggestFileRef (kHordeRootFile); + + ErrCode result = Hordes::LoadState (fileRef); + + if (result == 0) + { + // There are no events to load, but we need to make sure we at + // least clear out any old events. + + EmEventPlayback::Clear (); + } + + return result; +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::SaveSuspendedState + * + * DESCRIPTION: Creates a file reference to where the suspended state + * should be saved. Then calls a platform-specific + * routine to do the actual saving. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void +Hordes::SaveSuspendedState (void) +{ + EmFileRef fileRef = Hordes::SuggestFileRef (kHordeSuspendFile); + + gSession->Save (fileRef, false); + + // This sort of overloads the function, but right now, any time we + // save the suspend state, we also want to save any recorded events. + + Hordes::SaveEvents (); +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::LoadSuspendedState + * + * DESCRIPTION: Creates a file reference to where the suspended state + * should be loaded. Then calls a routine to do the actual + * loading. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +ErrCode +Hordes::LoadSuspendedState (void) +{ + EmFileRef fileRef = Hordes::SuggestFileRef (kHordeSuspendFile); + + ErrCode result = Hordes::LoadState (fileRef); + + if (result == 0) + { + // Load the events from the associated file holding them. Do this + // *after* Hordes::LoadState, as gSession->Load will try to load + // events from *its* file, overwriting the ones in our holding file. + // + // Note also that we load events in LoadSuspendedState but not + // LoadRootState, as there should not be any events associated + // with the root state (none have been generated!). + + Hordes::LoadEvents (); + } + + return result; +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::SaveEvents + * + * DESCRIPTION: Write out the current set of events to the designated + * event file. This file contains the root (initial) + * Gremlin state, to which we append the events. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void +Hordes::SaveEvents (void) +{ + EmFileRef eventRef = Hordes::SuggestFileRef (kHordeEventFile); + + EmStreamFile eventStream (eventRef, kCreateOrEraseForWrite, + kFileCreatorEmulator, kFileTypeEvents); + ChunkFile eventChunkFile (eventStream); + SessionFile eventSessionFile (eventChunkFile); + + // Copy over the root state to the session file, first. + + EmFileRef rootRef = Hordes::SuggestFileRef (kHordeRootFile); + EmStreamFile rootStream (rootRef, kOpenExistingForRead, + kFileCreatorEmulator, kFileTypeEvents); + + { + int32 length = rootStream.GetLength (); + ByteList buffer (length); + rootStream.GetBytes (&buffer[0], length); + eventStream.PutBytes (&buffer[0], length); + } + + // Finally, write the events to the file. + + EmEventPlayback::SaveEvents (eventSessionFile); +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::LoadEvents + * + * DESCRIPTION: Load the events from the current event file. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void +Hordes::LoadEvents (void) +{ + EmFileRef eventRef = Hordes::SuggestFileRef (kHordeEventFile); + + EmStreamFile stream (eventRef, kOpenExistingForRead, + kFileCreatorEmulator, kFileTypeEvents); + ChunkFile chunkFile (stream); + SessionFile eventFile (chunkFile); + + EmEventPlayback::LoadEvents (eventFile); +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::StartLog + * + * DESCRIPTION: Starts Hordes logging + * + * PARAMETERS: none + * + * RETURNED: none + * + ***********************************************************************/ + +void +Hordes::StartLog (void) +{ + LogClear (); + LogStartNew (); + + LogAppendMsg ("********************************************************************************"); + LogAppendMsg ("************* Gremlin Hordes started"); + LogAppendMsg ("********************************************************************************"); + LogAppendMsg ("Running Gremlins %ld to %ld", gGremlinStartNumber, gGremlinStopNumber); + + if (gSwitchDepth != -1) + LogAppendMsg ("Will run each Gremlin %ld events at a time until all Gremlins have terminated in error", gSwitchDepth); + + else + LogAppendMsg ("Will run each Gremlin until all Gremlins have terminated in error", gSwitchDepth); + + if (gMaxDepth != -1) + LogAppendMsg ("or have reached a maximum of %ld events", gMaxDepth); + + LogAppendMsg ("********************************************************************************"); + + LogDump (); +} + + +/*********************************************************************** + * + * FUNCTION: Hordes::GetGremlinDirectory + * + * DESCRIPTION: Return an EmDirRef for directory where information + * about the current Gremlin is saved. + * + * PARAMETERS: None + * + * RETURNED: The desired EmDirRef. + * + ***********************************************************************/ + +EmDirRef +Hordes::GetGremlinDirectory (void) +{ + // If requested, create the directory to use. + + if (gForceNewHordesDirectory) + { + gForceNewHordesDirectory = false; + + time_t now_time; + time (&now_time); + struct tm* now_tm = localtime (&now_time); + + char buffer[30]; + strftime (buffer, countof (buffer), "%Y-%m-%d.%H-%M-%S", now_tm); // 19 chars + NULL + + char stateName[30]; + sprintf (stateName, "Gremlins.%s", buffer); + + EmAssert (strlen(stateName) <= 31); // Max on Macs + + EmDirRef homeDir; + + if (!Hordes::GetGremlinsHome (homeDir)) + { + homeDir = EmDirRef::GetEmulatorDirectory (); + } + + gGremlinDir = EmDirRef (homeDir, stateName); + + if (!gGremlinDir.Exists ()) + { + try + { + gGremlinDir.Create (); + } + catch (...) + { + // !!! Put up some error message + + gGremlinDir = EmDirRef (); + } + } + } + + // Otherwise, return the previously specified directory. + + return gGremlinDir; +} + + +// --------------------------------------------------------------------------- +// ¥ Hordes::UseNewAutoSaveDirectory +// --------------------------------------------------------------------------- + +void Hordes::UseNewAutoSaveDirectory (void) +{ + gForceNewHordesDirectory = true; +} + + +// --------------------------------------------------------------------------- +// ¥ Hordes::ComputeStatistics +// --------------------------------------------------------------------------- + +void Hordes::ComputeStatistics (int32 &min, + int32 &max, + int32 &avg, + int32 &stdDev, + int32 &smallErrorIndex) +{ + // I'm worried about overflow, but in practice this should be sufficiently large + + int32 sum = 0; + + // this is the largest value possible with an int32; it is effectively infinity + + min = 0x7FFFFFFF; + max = 0; + + // initialize this to a sentinel value + + smallErrorIndex = 0x7FFFFFFF; + + int32 numEventsToErr = 0; + int32 counter = 0; + int32 errorCounter = 0; + + EmAssert (gGremlinStartNumber <= gGremlinStopNumber); + + for (counter = gGremlinStartNumber; counter <= gGremlinStopNumber; counter++) + { + numEventsToErr = gGremlinHaltedInError[counter].fErrorEvent; + sum += numEventsToErr; + + if (numEventsToErr > max) + { + max = numEventsToErr; + } + + if (numEventsToErr < min && numEventsToErr > 0) + { + min = numEventsToErr; + } + + if (numEventsToErr != 0) + { + errorCounter++; + + if ((smallErrorIndex == 0x7FFFFFFF) || + (numEventsToErr < gGremlinHaltedInError[smallErrorIndex].fErrorEvent)) + { + smallErrorIndex = counter; + } + } + } + + if (sum > 0 && errorCounter > 0) + { + avg = sum / errorCounter; + } + else + { + avg = 0; + stdDev = 0; + + return; + } + + // now to calculate the standard deviation + + int32 diffSquaredSum = 0; + + for (counter = gGremlinStartNumber; counter <= gGremlinStopNumber; counter++) + { + numEventsToErr = gGremlinHaltedInError[counter].fErrorEvent - avg; + diffSquaredSum += (numEventsToErr * numEventsToErr); + } + + // I'm assuming that MAXGREMLINS is not 0. Since it is defined, and + // constant, I think this is a safe assumption. + + diffSquaredSum /= MAXGREMLINS; // total - 1 + + stdDev = (int32) sqrt (diffSquaredSum); + + return; +} + + +// --------------------------------------------------------------------------- +// ¥ Hordes::GremlinReport +// --------------------------------------------------------------------------- + +void Hordes::GremlinReport (void) +{ + int32 counter = 0; + int32 lastSmallIndex = 0; + int32 numGremlinsWithError = 0; + int32 highestFrequency = 1; + + // We want a table of sorts showing all of the errors encountered, + // sorted by frequency. Errors that didn't crop up will be omitted, + // as they are uninteresting. Alongside each error will be the count + // of how many discrete Gremlins terminated with the error, and the + // Gremlin number of the offender which terminated after the least + // number of events. The errors that will be handled have their + // constants defined in Strings.r.h + + // There are several interesting problems to deal with: the error + // distribution, as compared to the number of Gremlins that were run, + // is likely to be quite low; we would like to come up with the + // Gremlin that terminates quickest, for each error; and we would like + // to know which error is most prevalent. + + // According to Strings.r.h, error codes will be in the range {1000, 1199}. + // Let's set up some offsets, and an array that will eventually hold + // the error prevalence data. + + const int32 errorBase = 1000; + const int32 errorLast = 1199; + + EmGremlinErrorFrequencyInfo errorCountArray [errorLast - errorBase + 1]; + + // Let's initialize the array, using the offset notation that will be + // used further on, just to be consistent. The first field will contain + // the count of the error, the second will contain the Gremlin that + // terminated quickest with the error. + + for (counter = errorBase; counter <= errorLast; counter++) + { + errorCountArray [counter - errorBase].fCount = 0; + errorCountArray [counter - errorBase].fErrorFrequency = 0; + + // sentinel value, so that we know that we haven't seen this error before + + errorCountArray [counter - errorBase].fFirstErrantGremlinIndex = -1; + } + + // Now let's walk through the gGremlinHaltedInError array, and for each + // Gremlin that terminated in error, increment the error count for the + // appropriate error + + int32 errorTypeNumber = 0; + int32 temp = 0; + + EmAssert (gGremlinStartNumber <= gGremlinStopNumber); + for (counter = gGremlinStartNumber; counter <= gGremlinStopNumber; counter++) + { + if (gGremlinHaltedInError[counter].fHalted != false) + { + errorTypeNumber = gGremlinHaltedInError[counter].fMessageID - errorBase; + + errorCountArray[errorTypeNumber].fErrorFrequency += 1; + numGremlinsWithError += 1; + + // if we have not seen this error before, then this Gremlin's index + // is, by definition, the index of the Gremlin which terminated first + // with this error. + + lastSmallIndex = errorCountArray[errorTypeNumber].fFirstErrantGremlinIndex; + + if (lastSmallIndex == -1) + { + errorCountArray[errorTypeNumber].fCount = gGremlinHaltedInError[counter].fErrorEvent; + errorCountArray[errorTypeNumber].fFirstErrantGremlinIndex = counter; + } + + // otherwise, we have seen this error before, so check whether this + // Gremlin terminated before the last frontrunner did. + + else + { + highestFrequency += 1; + + temp = gGremlinHaltedInError[counter].fErrorEvent; + + if (temp < gGremlinHaltedInError[lastSmallIndex].fErrorEvent) + { + errorCountArray[errorTypeNumber].fCount = temp; + errorCountArray[errorTypeNumber].fFirstErrantGremlinIndex = counter; + } + } + } + } + + // So now we have an array filled with the number of times each error occurred, + // and the # of the first-offending Gremlin. Now all that remains is to sort + // the errors by their frequency, and then output our results. Some vital + // information about the frequencies: their sum is numGremlinsWithError, and + // the highest frequency of any single error is <= the number of Gremlins with + // errors - 2 * the number of discrete errors that occurred multiple times. + // With these constraints in mind, this following algorithm, ostensibly n^2 + // time, is in reality of managable time since n is constrained as above. + // Why I am doing this is because I don't think that n is sufficiently large + // to warrant the overhead of a quicksort, and because I don't feel like + // writing a quicksort for a multi-dimensional array. + + if (numGremlinsWithError == 0) + { + return; + } + + LogAppendMsg ("%d of %d Gremlins terminated in error.", numGremlinsWithError, + gGremlinStopNumber - gGremlinStartNumber + 1); + LogAppendMsg (""); + LogAppendMsg ("Count Error name Shortest Gremlin Events"); + + int32 errorNumber = 0; + int32 frequency = 0; + string str; + + // We only go down to 1, since we don't care about the errors that didn't + // occur. + + for (frequency = highestFrequency; + frequency > 0; + frequency--) + { + for (errorNumber = errorBase; errorNumber <= errorLast; errorNumber++) + { + if (errorCountArray[errorNumber - errorBase].fErrorFrequency == frequency) + { + str = Hordes::TranslateErrorCode (errorNumber); + LogAppendMsg ("%-4d %-29s Gremlin #%-3d %-4d", + frequency, + str.c_str (), + errorCountArray[errorNumber - errorBase].fFirstErrantGremlinIndex, + errorCountArray[errorNumber - errorBase].fCount); + } + } + } +} + + +// --------------------------------------------------------------------------- +// ¥ Hordes::GetAppList +// --------------------------------------------------------------------------- +// Return the of applications that can be run under this Gremlin. + +DatabaseInfoList Hordes::GetAppList (void) +{ + // If it looks like some of the fields need to filled out, + // then do that now (some versions of Poser saved only the + // creator, type, and version fields, leaving the others blank + // -- we have to put back that information now). + + if (gGremlinAppList.size () > 0 && gGremlinAppList.front ().dbID == 0) + { + // Get the list of applications. + + DatabaseInfoList appList; + ::GetDatabases (appList, kApplicationsOnly); + + // Iterate over all the allowed Gremlins applications and all the + // installed applications, and use information in the latter to + // fill in the former. + + DatabaseInfoList::iterator gremlin_iter = gGremlinAppList.begin (); + while (gremlin_iter != gGremlinAppList.end ()) + { + DatabaseInfoList::iterator installed_iter = appList.begin (); + while (installed_iter != appList.end ()) + { + if (gremlin_iter->creator == installed_iter->creator && + gremlin_iter->type == installed_iter->type && + gremlin_iter->version == installed_iter->version) + { + *gremlin_iter = *installed_iter; + break; + } + + ++installed_iter; + } + + ++gremlin_iter; + } + } + + return gGremlinAppList; +} + + +// --------------------------------------------------------------------------- +// ¥ Hordes::TranslateErrorCode +// --------------------------------------------------------------------------- + +string Hordes::TranslateErrorCode (UInt32 errCode) +{ + switch (errCode) + { + case kStr_OpError: + return "OpError"; + break; + + case kStr_OpErrorRecover: + return "OpErrorRecover"; + break; + + case kStr_ErrBusError: + return "ErrBusError"; + break; + + case kStr_ErrAddressError: + return "ErrAddressError"; + break; + + case kStr_ErrIllegalInstruction: + return "ErrIllegalInstruction"; + break; + + case kStr_ErrDivideByZero: + return "ErrDivideByZero"; + break; + + case kStr_ErrCHKInstruction: + return "ErrCHKInstruction"; + break; + + case kStr_ErrTRAPVInstruction: + return "ErrTRAPVInstruction"; + break; + + case kStr_ErrPrivilegeViolation: + return "ErrPrivilegeViolation"; + break; + + case kStr_ErrTrace: + return "ErrTrace"; + break; + + case kStr_ErrATrap: + return "ErrATrap"; + break; + + case kStr_ErrFTrap: + return "ErrFTrap"; + break; + + case kStr_ErrTRAPx: + return "ErrTRAPx"; + break; + + case kStr_ErrStorageHeap: + return "ErrStorageHeap"; + break; + + case kStr_ErrNoDrawWindow: + return "ErrNoDrawWindow"; + break; + + case kStr_ErrNoGlobals: + return "ErrNoGlobals"; + break; + + case kStr_ErrSANE: + return "ErrSANE"; + break; + + case kStr_ErrTRAP0: + return "ErrTRAP0"; + break; + + case kStr_ErrTRAP8: + return "ErrTRAP8"; + break; + + case kStr_ErrStackOverflow: + return "ErrStackOverflow"; + break; + + case kStr_ErrUnimplementedTrap: + return "ErrUnimplementedTrap"; + break; + + case kStr_ErrInvalidRefNum: + return "ErrInvalidRefNum"; + break; + + case kStr_ErrCorruptedHeap: + return "ErrCorruptedHeap"; + break; + + case kStr_ErrInvalidPC1: + return "ErrInvalidPC1"; + break; + + case kStr_ErrInvalidPC2: + return "ErrInvalidPC2"; + break; + + case kStr_ErrLowMemory: + return "ErrLowMemory"; + break; + + case kStr_ErrSystemGlobals: + return "ErrSystemGlobals"; + break; + + case kStr_ErrScreen: + return "ErrScreen"; + break; + + case kStr_ErrHardwareRegisters: + return "ErrHardwareRegisters"; + break; + + case kStr_ErrROM: + return "ErrROM"; + break; + + case kStr_ErrMemMgrStructures: + return "ErrMemMgrStructures"; + break; + + case kStr_ErrMemMgrSemaphore: + return "ErrMemMgrSemaphore"; + break; + + case kStr_ErrFreeChunk: + return "ErrFreeChunk"; + break; + + case kStr_ErrUnlockedChunk: + return "ErrUnlockedChunk"; + break; + + case kStr_ErrLowStack: + return "ErrLowStack"; + break; + + case kStr_ErrStackFull: + return "ErrStackFull"; + break; + + case kStr_ErrSizelessObject: + return "ErrSizelessObject"; + break; + + case kStr_ErrOffscreenObject: + return "ErrOffscreenObject"; + break; + + case kStr_ErrFormAccess: + return "ErrFormAccess"; + break; + + case kStr_ErrFormObjectListAccess: + return "ErrFormObjectListAccess"; + break; + + case kStr_ErrFormObjectAccess: + return "ErrFormObjectAccess"; + break; + + case kStr_ErrWindowAccess: + return "ErrWindowAccess"; + break; + + case kStr_ErrBitmapAccess: + return "ErrBitmapAccess"; + break; + + case kStr_ErrProscribedFunction: + return "ErrProscribedFunction"; + break; + + case kStr_ErrStepSpy: + return "ErrStepSpy"; + break; + + case kStr_ErrWatchpoint: + return "ErrWatchpoint"; + break; + + case kStr_ErrSysFatalAlert: + return "ErrSysFatalAlert"; + break; + + case kStr_ErrDbgMessage: + return "ErrDbgMessage"; + break; + + case kStr_BadChecksum: + return "BadChecksum"; + break; + + case kStr_UnknownDeviceWarning: + return "UnknownDeviceWarning"; + break; + + case kStr_UnknownDeviceError: + return "UnknownDeviceError"; + break; + + case kStr_MissingSkins: + return "MissingSkins"; + break; + + case kStr_InconsistentDatabaseDates: + return "InconsistentDatabaseDates"; + break; + + case kStr_NULLDatabaseDate: + return "NULLDatabaseDate"; + break; + + case kStr_NeedHostFS: + return "NeedHostFS"; + break; + + case kStr_InvalidAddressNotEven: + return "InvalidAddressNotEven"; + break; + + case kStr_InvalidAddressNotInROMOrRAM: + return "InvalidAddressNotInROMOrRAM"; + break; + + case kStr_CannotParseCondition: + return "CannotParseCondition"; + break; + + case kStr_UserNameTooLong: + return "UserNameTooLong"; + break; + + case kStr_ErrMemoryLeak: + return "ErrMemoryLeak"; + break; + + case kStr_ErrMemoryLeaks: + return "ErrMemoryLeaks"; + break; + + default: + EmAssert (false); + } + + return ""; +} + |