quake4-sdk/source/mpgame/gamesys/State.cpp

425 lines
10 KiB
C++
Raw Permalink Normal View History

2007-06-15 00:00:00 +00:00
#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<stateCall_t> 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<idClass>* 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<stateCall_t>* node = states.NextNode(); node; node = node->NextNode() ) {
node->Owner()->Save( saveFile );
}
saveFile->WriteInt( interrupted.Num() );
for( idLinkList<stateCall_t>* 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 );
}
}