mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-12 08:11:10 +02:00
497 lines
10 KiB
C++
497 lines
10 KiB
C++
#include "precompiled.h"
|
|
|
|
#include "../Game_local.h"
|
|
|
|
const int HISTORY_COUNT = 50;
|
|
|
|
/*
|
|
=====================
|
|
stateParms_t::Wait
|
|
=====================
|
|
*/
|
|
void stateParms_t::Wait( float seconds )
|
|
{
|
|
time = gameLocal.time + SEC2MS( seconds );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
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 );
|
|
// 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 )
|
|
{
|
|
saveFile->ReadString( state );
|
|
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 )
|
|
{
|
|
stateCall_t* call;
|
|
call = new stateCall_t;
|
|
call->state = name;
|
|
call->delay = delay;
|
|
call->flags = flags;
|
|
call->parms.blendFrames = blendFrames;
|
|
call->parms.time = -1;
|
|
call->parms.stage = 0;
|
|
call->parms.substage = 0;
|
|
call->parms.param1 = 0;
|
|
call->parms.param2 = 0;
|
|
|
|
call->parms.subparam1 = 0;
|
|
call->parms.subparam2 = 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() ) ? 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 );
|
|
//owner->SetStateParms(call->parms);
|
|
owner->Invoke( 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 );
|
|
// owner->SetStateParms(call->parms);
|
|
owner->Invoke( 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( gameLocal.time < call->parms.time + call->delay )
|
|
{
|
|
fl.executing = false;
|
|
return SRESULT_WAIT;
|
|
}
|
|
|
|
// Debugging
|
|
if( lastResult != SRESULT_WAIT )
|
|
{
|
|
// Keep a history of the called states so we can dump them on an overflow
|
|
historyState[historyEnd] = call->state;
|
|
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;
|
|
stateStage = call->parms.stage;
|
|
|
|
if( g_debugState.GetBool() )
|
|
{
|
|
if( call->parms.stage )
|
|
{
|
|
gameLocal.Printf( "%s: %s (%d)\n", ((idEntity *)owner)->GetClassname(), call->state.c_str(), call->parms.stage );
|
|
}
|
|
else
|
|
{
|
|
gameLocal.Printf( "%s: %s\n", ((idEntity*)owner)->GetClassname(), call->state.c_str() );
|
|
}
|
|
}
|
|
|
|
// Actually call the state function
|
|
lastResult = ( stateResult_t )owner->Invoke( call->state, &call->parms );
|
|
switch( lastResult )
|
|
{
|
|
case SRESULT_DONE_FRAME:
|
|
fl.executing = false;
|
|
return SRESULT_DONE;
|
|
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;
|
|
}
|
|
|
|
if (lastResult == SRESULT_DONE) {
|
|
owner->StateThreadChanged();
|
|
}
|
|
|
|
// 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 );
|
|
}
|
|
}
|