#include "../../idlib/precompiled.h" #pragma hdrstop #include "../Game_local.h" const int HISTORY_COUNT = 50; /* ===================== stateParms_t::Save ===================== */ void stateParms_t::Save( idSaveGame *saveFile ) const { saveFile->WriteInt( blendFrames ); saveFile->WriteInt( time ); saveFile->WriteInt( stage ); } /* ===================== stateParms_t::Restore ===================== */ void stateParms_t::Restore( idRestoreGame *saveFile ) { saveFile->ReadInt( blendFrames ); saveFile->ReadInt( time ); saveFile->ReadInt( stage ); } /* ===================== stateCall_t::Save ===================== */ void stateCall_t::Save( idSaveGame *saveFile ) const { saveFile->WriteString( state->name ); // TOSAVE: idLinkList node; saveFile->WriteInt( flags ); saveFile->WriteInt( delay ); parms.Save( saveFile ); } /* ===================== stateCall_t::Save ===================== */ void stateCall_t::Restore( idRestoreGame *saveFile, const idClass* owner ) { idStr name; saveFile->ReadString( name ); state = owner->FindState( name ); saveFile->ReadInt( flags ); saveFile->ReadInt( delay ); parms.Restore( saveFile ); } /* ===================== rvStateThread::rvStateThread ===================== */ rvStateThread::rvStateThread ( void ) { owner = NULL; insertAfter = NULL; lastResult = SRESULT_DONE; states.Clear ( ); interrupted.Clear ( ); memset ( &fl, 0, sizeof(fl) ); } /* ===================== rvStateThread::~rvStateThread ===================== */ rvStateThread::~rvStateThread ( void ) { Clear ( true ); } /* ===================== rvStateThread::SetOwner ===================== */ void rvStateThread::SetOwner ( idClass* _owner ) { owner = _owner; } /* ===================== rvStateThread::Post ===================== */ stateResult_t rvStateThread::PostState ( const char* name, int blendFrames, int delay, int flags ) { const rvStateFunc* func; // Make sure the state exists before queueing it if ( NULL == (func = owner->FindState ( name ) ) ) { return SRESULT_ERROR; } stateCall_t* call; call = new stateCall_t; call->state = func; call->delay = delay; call->flags = flags; call->parms.blendFrames = blendFrames; call->parms.time = -1; call->parms.stage = 0; call->node.SetOwner ( call ); if ( fl.executing && insertAfter ) { call->node.InsertAfter ( insertAfter->node ); } else { call->node.AddToEnd ( states ); } insertAfter = call; return SRESULT_OK; } /* ===================== rvStateThread::Set ===================== */ stateResult_t rvStateThread::SetState ( const char* name, int blendFrames, int delay, int flags ) { Clear ( ); return PostState ( name, blendFrames, delay, flags ); } /* ===================== rvStateThread::InterruptState ===================== */ stateResult_t rvStateThread::InterruptState ( const char* name, int blendFrames, int delay, int flags ) { stateCall_t* call; // Move all states to the front of the interrupted list in the same order for ( call = states.Prev(); call; call = states.Prev() ) { call->node.Remove ( ); call->node.AddToFront ( interrupted ); } // Nothing to insert after anymore insertAfter = NULL; fl.stateInterrupted = true; // Post the state now return PostState ( name, blendFrames, delay, flags ); } /* ===================== rvStateThread::CurrentStateIs ===================== */ bool rvStateThread::CurrentStateIs( const char* name ) const { return ( !IsIdle() ) ? owner->FindState(name) == GetState()->state : false; } /* ===================== rvStateThread::Clear ===================== */ void rvStateThread::Clear ( bool ignoreStateCalls ) { stateCall_t* call; // Clear all states from the main state list for( call = states.Next(); call != NULL; call = states.Next() ) { if ( !ignoreStateCalls && (call->flags & (SFLAG_ONCLEAR|SFLAG_ONCLEARONLY) ) ) { owner->ProcessState ( call->state, call->parms ); } call->node.Remove(); delete call; } // Clear all interrupted states for( call = interrupted.Next(); call != NULL; call = interrupted.Next() ) { if ( !ignoreStateCalls && (call->flags & (SFLAG_ONCLEAR|SFLAG_ONCLEARONLY) ) ) { owner->ProcessState ( call->state, call->parms ); } call->node.Remove(); delete call; } insertAfter = NULL; fl.stateCleared = true; states.Clear ( ); interrupted.Clear ( ); } /* ===================== rvStateThread::Execute ===================== */ stateResult_t rvStateThread::Execute ( void ) { stateCall_t* call = NULL; int count; const char* stateName; int stateStage; const char* historyState[HISTORY_COUNT]; int historyStage[HISTORY_COUNT]; int historyStart; int historyEnd; // If our main state loop is empty copy over any states in the interrupted state if ( !states.Next ( ) ) { for ( call = interrupted.Next(); call; call = interrupted.Next() ) { call->node.Remove ( ); call->node.AddToEnd ( states ); } assert ( !interrupted.Next ( ) ); } // State thread is idle if there are no states if ( !states.Next() ) { return SRESULT_IDLE; } fl.executing = true; // Run through the states until there are no more or one of them tells us to wait count = 0; historyStart = 0; historyEnd = 0; for( call = states.Next(); call && count < HISTORY_COUNT; call = states.Next(), ++count ) { insertAfter = call; fl.stateCleared = false; fl.stateInterrupted = false; // If this state is only called when being cleared then just skip it if ( call->flags & SFLAG_ONCLEARONLY ) { call->node.Remove ( ); delete call; continue; } // If the call has a delay on it the time will be set to negative initially and then // converted to game time. if ( call->parms.time <= 0 ) { call->parms.time = gameLocal.time; } // Check for delayed states if ( call->delay && gameLocal.time < call->parms.time + call->delay ) { fl.executing = false; return SRESULT_WAIT; } // Debugging if ( lastResult != SRESULT_WAIT ) { if ( *g_debugState.GetString ( ) && (*g_debugState.GetString ( ) == '*' || !idStr::Icmp ( g_debugState.GetString ( ), name ) ) ) { if ( call->parms.stage ) { gameLocal.Printf ( "%s: %s (%d)\n", name.c_str(), call->state->name, call->parms.stage ); } else { gameLocal.Printf ( "%s: %s\n", name.c_str(), call->state->name ); } } // Keep a history of the called states so we can dump them on an overflow historyState[historyEnd] = call->state->name; historyStage[historyEnd] = call->parms.stage; historyEnd = (historyEnd+1) % HISTORY_COUNT; if ( historyEnd == historyStart ) { historyStart = (historyEnd+1) % HISTORY_COUNT; } } // Cache name and stage for error messages stateName = call->state->name; stateStage = call->parms.stage; // Actually call the state function lastResult = owner->ProcessState ( call->state, call->parms ); switch ( lastResult ) { case SRESULT_WAIT: fl.executing = false; return SRESULT_WAIT; case SRESULT_ERROR: gameLocal.Error ( "rvStateThread: error reported by state '%s (%d)'", stateName, stateStage ); fl.executing = false; return SRESULT_ERROR; } // Dont remove the node if it was interrupted or cleared in the last process if ( !fl.stateCleared && !fl.stateInterrupted ) { if( lastResult >= SRESULT_SETDELAY ) { call->delay = lastResult - SRESULT_SETDELAY; call->parms.time = gameLocal.GetTime(); continue; } else if ( lastResult >= SRESULT_SETSTAGE ) { call->parms.stage = lastResult - SRESULT_SETSTAGE; continue; } // Done with state so remove it from list call->node.Remove ( ); delete call; } // Finished the last state but wait a frame for next one if ( lastResult == SRESULT_DONE_WAIT ) { fl.executing = false; return SRESULT_WAIT; } } // Runaway state loop? if ( count >= HISTORY_COUNT ) { idFile *file; fileSystem->RemoveFile ( "statedump.txt" ); file = fileSystem->OpenFileWrite( "statedump.txt" ); for ( ; historyStart != historyEnd; historyStart = (historyStart + 1) % HISTORY_COUNT ) { if ( historyStage[historyStart] ) { gameLocal.Printf ( "rvStateThread: %s (%d)\n", historyState[historyStart], historyStage[historyStart] ); } else { gameLocal.Printf ( "rvStateThread: %s\n", historyState[historyStart] ); } if ( file ) { if ( historyStage[historyStart] ) { file->Printf ( "rvStateThread: %s (%d)\n", historyState[historyStart], historyStage[historyStart] ); } else { file->Printf ( "rvStateThread: %s\n", historyState[historyStart] ); } } } if ( file ) { fileSystem->CloseFile( file ); } gameLocal.Error ( "rvStateThread: run away state loop '%s'", name.c_str() ); } insertAfter = NULL; fl.executing = false; // Move interrupted states back into the main state list when the main state list is empty if ( !states.Next() && interrupted.Next ( ) ) { return Execute ( ); } return lastResult; } /* ===================== rvStateThread::Save ===================== */ void rvStateThread::Save( idSaveGame *saveFile ) const { saveFile->WriteString( name.c_str() ); // No need to save owner, its setup in restore saveFile->WriteInt( lastResult ); saveFile->Write ( &fl, sizeof(fl) ); saveFile->WriteInt( states.Num() ); for( idLinkList* node = states.NextNode(); node; node = node->NextNode() ) { node->Owner()->Save( saveFile ); } saveFile->WriteInt( interrupted.Num() ); for( idLinkList* node = interrupted.NextNode(); node; node = node->NextNode() ) { node->Owner()->Save( saveFile ); } // TOSAVE: stateCall_t* insertAfter; // TOSAVE: stateResult_t lastResult; } /* ===================== rvStateThread::Restore ===================== */ void rvStateThread::Restore( idRestoreGame *saveFile, idClass* owner ) { int numStates; stateCall_t* call = NULL; saveFile->ReadString( name ); this->owner = owner; saveFile->ReadInt( (int&)lastResult ); saveFile->Read ( &fl, sizeof(fl) ); saveFile->ReadInt( numStates ); for( ; numStates > 0; numStates-- ) { call = new stateCall_t; assert( call ); call->Restore( saveFile, owner ); call->node.SetOwner ( call ); call->node.AddToEnd ( states ); } saveFile->ReadInt( numStates ); for( ; numStates > 0; numStates-- ) { call = new stateCall_t; assert( call ); call->Restore( saveFile, owner ); call->node.SetOwner ( call ); call->node.AddToEnd ( interrupted ); } }