Added base class AIStateMachine.
This is the skeleton needed to implement classes that can be reused and work together, which can perform asynchronous tasks (read: need to wait for certain events before they can continue). An example would be the task of waiting for a given inventory folder to be read. This could then be used to improve the builtin AO (automatically reading that folder when a notecard is dropped, and continuing when the whole folder is read). It's first use will be communication with a filepicker that runs in a plugin.
This commit is contained in:
@@ -114,6 +114,7 @@ Aleric Inglewood
|
||||
IMP-670
|
||||
IMP-701
|
||||
IMP-734
|
||||
IMP-735
|
||||
Alissa Sabre
|
||||
VWR-81
|
||||
VWR-83
|
||||
|
||||
@@ -171,6 +171,7 @@ void stop_recording_backtraces(void)
|
||||
channel_ct gtk DDCN("GTK"); //!< This debug channel is used for output related to gtk.
|
||||
channel_ct sdl DDCN("SDL"); //!< This debug channel is used for output related to sdl locking.
|
||||
channel_ct backtrace DDCN("BACKTRACE"); //!< This debug channel is used for backtraces.
|
||||
channel_ct statemachine DDCN("STATEMACHINE"); //!< This debug channel is used class AIStateMachine.
|
||||
|
||||
} // namespace dc
|
||||
} // namespace DEBUGCHANNELS
|
||||
|
||||
@@ -116,6 +116,7 @@ extern CWD_API channel_ct primbackup;
|
||||
extern CWD_API channel_ct gtk;
|
||||
extern CWD_API channel_ct sdl;
|
||||
extern CWD_API channel_ct backtrace;
|
||||
extern CWD_API channel_ct statemachine;
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -177,6 +177,7 @@ public:
|
||||
FTM_REFRESH,
|
||||
FTM_SORT,
|
||||
FTM_PICK,
|
||||
FTM_STATEMACHINE,
|
||||
|
||||
// Temp
|
||||
FTM_TEMP1,
|
||||
|
||||
@@ -1005,6 +1005,15 @@ set(viewer_HEADER_FILES
|
||||
|
||||
source_group("CMake Rules" FILES ViewerInstall.cmake)
|
||||
|
||||
set(statemachine_SOURCE_FILES
|
||||
statemachine/aistatemachine.cpp
|
||||
)
|
||||
set(statemachine_HEADER_FILES
|
||||
statemachine/aistatemachine.h
|
||||
)
|
||||
list(APPEND viewer_SOURCE_FILES ${statemachine_SOURCE_FILES})
|
||||
list(APPEND viewer_HEADER_FILES ${statemachine_HEADER_FILES})
|
||||
|
||||
if (DARWIN)
|
||||
LIST(APPEND viewer_SOURCE_FILES llappviewermacosx.cpp)
|
||||
|
||||
|
||||
@@ -11382,6 +11382,17 @@
|
||||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
<key>StateMachineMaxTime</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Maximum time per frame spent executing AIStateMachine objects, in miliseconds</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>U32</string>
|
||||
<key>Value</key>
|
||||
<integer>20</integer>
|
||||
</map>
|
||||
<key>StatsAutoRun</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
|
||||
@@ -47,7 +47,7 @@ LLCallbackList gIdleCallbacks;
|
||||
// Member functions
|
||||
//
|
||||
|
||||
LLCallbackList::LLCallbackList()
|
||||
LLCallbackList::LLCallbackList() : mLoopingOverCallbackList(false)
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
@@ -96,7 +96,15 @@ BOOL LLCallbackList::deleteFunction( callback_t func, void *data)
|
||||
callback_list_t::iterator iter = std::find(mCallbackList.begin(), mCallbackList.end(), t);
|
||||
if (iter != mCallbackList.end())
|
||||
{
|
||||
mCallbackList.erase(iter);
|
||||
if (mLoopingOverCallbackList)
|
||||
{
|
||||
iter->first = NULL; // Mark for removal later (when we return to LLCallbackList::callFunctions).
|
||||
mNeedErase = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
mCallbackList.erase(iter);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
@@ -108,16 +116,38 @@ BOOL LLCallbackList::deleteFunction( callback_t func, void *data)
|
||||
|
||||
void LLCallbackList::deleteAllFunctions()
|
||||
{
|
||||
llassert(!mLoopingOverCallbackList); // Only called from unit tests.
|
||||
mCallbackList.clear();
|
||||
}
|
||||
|
||||
|
||||
void LLCallbackList::callFunctions()
|
||||
{
|
||||
for (callback_list_t::iterator iter = mCallbackList.begin(); iter != mCallbackList.end(); )
|
||||
llassert(!mLoopingOverCallbackList);
|
||||
mLoopingOverCallbackList = true;
|
||||
mNeedErase = false;
|
||||
for (callback_list_t::iterator iter = mCallbackList.begin(); iter != mCallbackList.end(); ++iter)
|
||||
{
|
||||
callback_list_t::iterator curiter = iter++;
|
||||
curiter->first(curiter->second);
|
||||
if (iter->first) // Not pending removal?
|
||||
{
|
||||
iter->first(iter->second); // This can theorectically set any iter->first to NULL, which means the entry should be erased.
|
||||
}
|
||||
}
|
||||
mLoopingOverCallbackList = false;
|
||||
if (mNeedErase)
|
||||
{
|
||||
callback_list_t::iterator iter = mCallbackList.begin();
|
||||
while (iter != mCallbackList.end())
|
||||
{
|
||||
if (!iter->first)
|
||||
{
|
||||
iter = mCallbackList.erase(iter);
|
||||
}
|
||||
else
|
||||
{
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,9 +53,11 @@ public:
|
||||
|
||||
protected:
|
||||
// Use a list so that the callbacks are ordered in case that matters
|
||||
typedef std::pair<callback_t,void*> callback_pair_t;
|
||||
typedef std::pair<callback_t,void*> callback_pair_t; // callback_t is a (function) pointer. If it is NULL it means that the entry should be considered deleted.
|
||||
typedef std::list<callback_pair_t > callback_list_t;
|
||||
callback_list_t mCallbackList;
|
||||
bool mLoopingOverCallbackList; // True while looping over mCallbackList and calling the callback_t functions (see callFunctions).
|
||||
bool mNeedErase; // True when deleteFunction was called while mLoopingOverCallbackList was true.
|
||||
};
|
||||
|
||||
extern LLCallbackList gIdleCallbacks;
|
||||
|
||||
@@ -83,6 +83,7 @@ static struct ft_display_info ft_display_table[] =
|
||||
{ LLFastTimer::FTM_KEYHANDLER, " Keyboard", &LLColor4::grey1, 0 },
|
||||
{ LLFastTimer::FTM_SLEEP, " Sleep", &LLColor4::grey2, 0 },
|
||||
{ LLFastTimer::FTM_IDLE, " Idle", &blue0, 0 },
|
||||
{ LLFastTimer::FTM_STATEMACHINE, " State Machines", &LLColor4::yellow1, 0 },
|
||||
{ LLFastTimer::FTM_PUMP, " Pump", &LLColor4::magenta2, 1 },
|
||||
{ LLFastTimer::FTM_CURL, " Curl", &LLColor4::magenta3, 0 },
|
||||
{ LLFastTimer::FTM_PUMPIO, " PumpIO", &LLColor4::magenta1, 0 },
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
#include "llnetmap.h"
|
||||
#include "llrender.h"
|
||||
#include "llfloaterchat.h"
|
||||
#include "statemachine/aistatemachine.h"
|
||||
#include "aithreadsafe.h"
|
||||
#include "llviewerobjectlist.h"
|
||||
#include "lldrawpoolbump.h"
|
||||
@@ -119,6 +120,11 @@ static bool handleTerrainDetailChanged(const LLSD& newvalue)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool handleStateMachineMaxTimeChanged(const LLSD& newvalue)
|
||||
{
|
||||
AIStateMachine::updateSettings();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool handleSetShaderChanged(const LLSD& newvalue)
|
||||
{
|
||||
@@ -707,6 +713,7 @@ void settings_setup_listeners()
|
||||
gSavedSettings.getControl("AudioLevelMic")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1));
|
||||
gSavedSettings.getControl("LipSyncEnabled")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1));
|
||||
gSavedSettings.getControl("TranslateChat")->getSignal()->connect(boost::bind(&handleTranslateChatPrefsChanged, _1));
|
||||
gSavedSettings.getControl("StateMachineMaxTime")->getSignal()->connect(boost::bind(&handleStateMachineMaxTimeChanged, _1));
|
||||
|
||||
gSavedSettings.getControl("CloudsEnabled")->getSignal()->connect(boost::bind(&handleCloudSettingsChanged, _1));
|
||||
gSavedSettings.getControl("SkyUseClassicClouds")->getSignal()->connect(boost::bind(&handleCloudSettingsChanged, _1));
|
||||
|
||||
343
indra/newview/statemachine/aistatemachine.cpp
Normal file
343
indra/newview/statemachine/aistatemachine.cpp
Normal file
@@ -0,0 +1,343 @@
|
||||
/**
|
||||
* @file aistatemachine.cpp
|
||||
* @brief Implementation of AIStateMachine
|
||||
*
|
||||
* Copyright (c) 2010, Aleric Inglewood.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* There are special exceptions to the terms and conditions of the GPL as
|
||||
* it is applied to this Source Code. View the full text of the exception
|
||||
* in the file doc/FLOSS-exception.txt in this software distribution.
|
||||
*
|
||||
* CHANGELOG
|
||||
* and additional copyright holders.
|
||||
*
|
||||
* 01/03/2010
|
||||
* Initial version, written by Aleric Inglewood @ SL
|
||||
*/
|
||||
|
||||
#include "../llviewerprecompiledheaders.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "../llcallbacklist.h"
|
||||
#include "../llviewercontrol.h"
|
||||
|
||||
#include "llfasttimer.h"
|
||||
#include "aithreadsafe.h"
|
||||
#include "aistatemachine.h"
|
||||
|
||||
// Local variables.
|
||||
namespace {
|
||||
struct QueueElementComp;
|
||||
|
||||
class QueueElement {
|
||||
private:
|
||||
AIStateMachine* mStateMachine;
|
||||
U64 mRuntime;
|
||||
|
||||
public:
|
||||
QueueElement(AIStateMachine* statemachine) : mStateMachine(statemachine), mRuntime(0) { }
|
||||
friend bool operator==(QueueElement const& e1, QueueElement const& e2) { return e1.mStateMachine == e2.mStateMachine; }
|
||||
friend struct QueueElementComp;
|
||||
|
||||
AIStateMachine& statemachine(void) const { return *mStateMachine; }
|
||||
void add(U64 count) { mRuntime += count; }
|
||||
};
|
||||
|
||||
struct QueueElementComp {
|
||||
bool operator()(QueueElement const& e1, QueueElement const& e2) const { return e1.mRuntime < e2.mRuntime; }
|
||||
};
|
||||
|
||||
typedef std::vector<QueueElement> active_statemachines_type;
|
||||
static active_statemachines_type active_statemachines;
|
||||
typedef std::vector<AIStateMachine*> continued_statemachines_type;
|
||||
struct cscm_type
|
||||
{
|
||||
continued_statemachines_type continued_statemachines;
|
||||
bool calling_mainloop;
|
||||
};
|
||||
static AITHREADSAFE(cscm_type, continued_statemachines_and_calling_mainloop, );
|
||||
}
|
||||
|
||||
// static
|
||||
AITHREADSAFESIMPLE(U64, AIStateMachine::sMaxCount, );
|
||||
|
||||
void AIStateMachine::updateSettings(void)
|
||||
{
|
||||
Dout(dc::statemachine, "Initializing AIStateMachine::sMaxCount");
|
||||
*AIAccess<U64>(sMaxCount) = LLFastTimer::countsPerSecond() * gSavedSettings.getU32("StateMachineMaxTime") / 1000;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
|
||||
void AIStateMachine::run(AIStateMachine* parent, state_type new_parent_state, bool abort_parent)
|
||||
{
|
||||
DoutEntering(dc::statemachine, "AIStateMachine::run(" << (void*)parent << ", " << (parent ? parent->state_str(new_parent_state) : "NA") << ", " << abort_parent << ") [" << (void*)this << "]");
|
||||
// Must be the first time we're being run.
|
||||
llassert(!mParent);
|
||||
llassert(!mCallback);
|
||||
// Can only be run when in this state.
|
||||
llassert(mState == bs_initialize);
|
||||
// If a parent is provided, it must be running.
|
||||
llassert(!parent || parent->mState == bs_run);
|
||||
|
||||
mParent = parent;
|
||||
mNewParentState = new_parent_state;
|
||||
mAbortParent = abort_parent;
|
||||
|
||||
cont();
|
||||
}
|
||||
|
||||
void AIStateMachine::run(callback_type::signal_type::slot_type const& slot)
|
||||
{
|
||||
DoutEntering(dc::statemachine, "AIStateMachine::run(<slot>) [" << (void*)this << "]");
|
||||
// Must be the first time we're being run.
|
||||
llassert(!mParent);
|
||||
llassert(!mCallback);
|
||||
// Can only be run when in this state.
|
||||
llassert(mState == bs_initialize);
|
||||
|
||||
mCallback = new callback_type(slot);
|
||||
|
||||
cont();
|
||||
}
|
||||
|
||||
void AIStateMachine::idle(void)
|
||||
{
|
||||
DoutEntering(dc::statemachine, "AIStateMachine::idle() [" << (void*)this << "]");
|
||||
llassert(!mIdle);
|
||||
mIdle = true;
|
||||
mSleep = 0;
|
||||
}
|
||||
|
||||
void AIStateMachine::cont(void)
|
||||
{
|
||||
DoutEntering(dc::statemachine, "AIStateMachine::cont() [" << (void*)this << "]");
|
||||
llassert(mIdle);
|
||||
mIdle = false;
|
||||
AIWriteAccess<cscm_type> cscm_w(continued_statemachines_and_calling_mainloop);
|
||||
cscm_w->continued_statemachines.push_back(this);
|
||||
if (!cscm_w->calling_mainloop)
|
||||
{
|
||||
Dout(dc::statemachine, "Adding AIStateMachine::mainloop to gIdleCallbacks");
|
||||
cscm_w->calling_mainloop = true;
|
||||
gIdleCallbacks.addFunction(&AIStateMachine::mainloop);
|
||||
}
|
||||
}
|
||||
|
||||
void AIStateMachine::set_state(state_type state)
|
||||
{
|
||||
DoutEntering(dc::statemachine, "AIStateMachine::set_state(" << state_str(state) << ") [" << (void*)this << "]");
|
||||
llassert(mState == bs_run);
|
||||
if (mRunState != state)
|
||||
{
|
||||
mRunState = state;
|
||||
Dout(dc::statemachine, "mRunState set to " << state_str(mRunState));
|
||||
}
|
||||
if (mIdle)
|
||||
cont();
|
||||
}
|
||||
|
||||
void AIStateMachine::abort(void)
|
||||
{
|
||||
DoutEntering(dc::statemachine, "AIStateMachine::abort() [" << (void*)this << "]");
|
||||
llassert(mState == bs_run);
|
||||
mState = bs_abort;
|
||||
abort_impl();
|
||||
mAborted = true;
|
||||
finish();
|
||||
}
|
||||
|
||||
void AIStateMachine::finish(void)
|
||||
{
|
||||
DoutEntering(dc::statemachine, "AIStateMachine::finish() [" << (void*)this << "]");
|
||||
llassert(mState == bs_run || mState == bs_abort);
|
||||
mState = bs_finish;
|
||||
finish_impl();
|
||||
if (mParent)
|
||||
{
|
||||
// It is possible that the parent is not running when the parent is in fact aborting and called
|
||||
// abort on this object from it's abort_impl function. It that case we don't want to recursively
|
||||
// call abort again (or change it's state).
|
||||
if (mParent->running())
|
||||
{
|
||||
if (mAborted && mAbortParent)
|
||||
{
|
||||
mParent->abort();
|
||||
}
|
||||
else
|
||||
{
|
||||
mParent->set_state(mNewParentState);
|
||||
}
|
||||
}
|
||||
mParent = NULL;
|
||||
}
|
||||
// Set this already to bs_initialize now, so that (bool)*this evaluates to true.
|
||||
mState = bs_initialize;
|
||||
// It is possible that mIdle is false when abort or finish was called from
|
||||
// outside multiplex_impl. However, that only may be done by the main thread.
|
||||
llassert(!mIdle || is_main_thread());
|
||||
if (!mIdle)
|
||||
idle();
|
||||
if (mCallback)
|
||||
{
|
||||
mCallback->callback(!mAborted); // This can/may call deleteMe(), in which case the whole AIStateMachine will be deleted from the mainloop.
|
||||
delete mCallback;
|
||||
mCallback = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void AIStateMachine::deleteMe(void)
|
||||
{
|
||||
llassert(mIdle && mState == bs_initialize);
|
||||
mState = bs_deleted;
|
||||
}
|
||||
|
||||
// Return stringified 'state'.
|
||||
char const* AIStateMachine::state_str(state_type state)
|
||||
{
|
||||
if (state >= min_state && state < max_state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
AI_CASE_RETURN(bs_initialize);
|
||||
AI_CASE_RETURN(bs_run);
|
||||
AI_CASE_RETURN(bs_abort);
|
||||
AI_CASE_RETURN(bs_finish);
|
||||
AI_CASE_RETURN(bs_deleted);
|
||||
}
|
||||
}
|
||||
return state_str_impl(state);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
|
||||
void AIStateMachine::multiplex(U64 current_time)
|
||||
{
|
||||
// Return immediately when this state machine is sleeping.
|
||||
// A negative value of mSleep means we're counting frames,
|
||||
// a positive value means we're waiting till a certain
|
||||
// amount of time has passed.
|
||||
if (mSleep != 0)
|
||||
{
|
||||
if (mSleep < 0)
|
||||
{
|
||||
if (++mSleep)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (current_time < mSleep)
|
||||
return;
|
||||
mSleep = 0;
|
||||
}
|
||||
}
|
||||
|
||||
DoutEntering(dc::statemachine, "AIStateMachine::multiplex() [" << (void*)this << "] [with state: " << state_str(mState == bs_run ? mRunState : mState) << "]");
|
||||
llassert(mState == bs_initialize || mState == bs_run);
|
||||
|
||||
// Real state machine starts here.
|
||||
if (mState == bs_initialize)
|
||||
{
|
||||
mAborted = false;
|
||||
mState = bs_run;
|
||||
initialize_impl();
|
||||
if (mAborted || mState != bs_run)
|
||||
return;
|
||||
}
|
||||
multiplex_impl();
|
||||
}
|
||||
|
||||
// static
|
||||
void AIStateMachine::mainloop(void*)
|
||||
{
|
||||
LLFastTimer t(LLFastTimer::FTM_STATEMACHINE);
|
||||
// Add continued state machines.
|
||||
{
|
||||
AIReadAccess<cscm_type> cscm_r(continued_statemachines_and_calling_mainloop);
|
||||
bool nonempty = false;
|
||||
for (continued_statemachines_type::const_iterator iter = cscm_r->continued_statemachines.begin(); iter != cscm_r->continued_statemachines.end(); ++iter)
|
||||
{
|
||||
nonempty = true;
|
||||
active_statemachines.push_back(QueueElement(*iter));
|
||||
Dout(dc::statemachine, "Adding " << (void*)*iter << " to active_statemachines");
|
||||
}
|
||||
if (nonempty)
|
||||
AIWriteAccess<cscm_type>(cscm_r)->continued_statemachines.clear();
|
||||
}
|
||||
llassert(!active_statemachines.empty());
|
||||
// Run one or more state machines.
|
||||
U64 total_clocks = 0;
|
||||
U64 max_count = *AIAccess<U64>(sMaxCount);
|
||||
for (active_statemachines_type::iterator iter = active_statemachines.begin(); iter != active_statemachines.end(); ++iter)
|
||||
{
|
||||
AIStateMachine& statemachine(iter->statemachine());
|
||||
if (!statemachine.mIdle)
|
||||
{
|
||||
U64 start = get_cpu_clock_count();
|
||||
iter->statemachine().multiplex(start);
|
||||
U64 delta = get_cpu_clock_count() - start;
|
||||
iter->add(delta);
|
||||
total_clocks += delta;
|
||||
if (total_clocks >= max_count)
|
||||
{
|
||||
#ifndef LL_RELEASE_FOR_DOWNLOAD
|
||||
llwarns << "AIStateMachine::mainloop did run for " << (total_clocks * 1000 / LLFastTimer::countsPerSecond()) << " ms." << llendl;
|
||||
#endif
|
||||
std::sort(active_statemachines.begin(), active_statemachines.end(), QueueElementComp());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove idle state machines from the loop.
|
||||
active_statemachines_type::iterator iter = active_statemachines.begin();
|
||||
while (iter != active_statemachines.end())
|
||||
{
|
||||
AIStateMachine& statemachine(iter->statemachine());
|
||||
if (statemachine.mIdle)
|
||||
{
|
||||
Dout(dc::statemachine, "Erasing " << (void*)&statemachine << " from active_statemachines");
|
||||
iter = active_statemachines.erase(iter);
|
||||
if (statemachine.mState == bs_deleted)
|
||||
{
|
||||
Dout(dc::statemachine, "Deleting " << (void*)&statemachine);
|
||||
delete &statemachine;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
llassert(statemachine.mState == bs_run);
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
if (active_statemachines.empty())
|
||||
{
|
||||
// If this was the last state machine, remove mainloop from the IdleCallbacks.
|
||||
AIReadAccess<cscm_type> cscm_r(continued_statemachines_and_calling_mainloop);
|
||||
if (cscm_r->continued_statemachines.empty() && cscm_r->calling_mainloop)
|
||||
{
|
||||
Dout(dc::statemachine, "Removing AIStateMachine::mainloop from gIdleCallbacks");
|
||||
AIWriteAccess<cscm_type>(cscm_r)->calling_mainloop = false;
|
||||
gIdleCallbacks.deleteFunction(&AIStateMachine::mainloop);
|
||||
}
|
||||
}
|
||||
}
|
||||
344
indra/newview/statemachine/aistatemachine.h
Normal file
344
indra/newview/statemachine/aistatemachine.h
Normal file
@@ -0,0 +1,344 @@
|
||||
/**
|
||||
* @file aistatemachine.h
|
||||
* @brief State machine base class
|
||||
*
|
||||
* Copyright (c) 2010, Aleric Inglewood.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* There are special exceptions to the terms and conditions of the GPL as
|
||||
* it is applied to this Source Code. View the full text of the exception
|
||||
* in the file doc/FLOSS-exception.txt in this software distribution.
|
||||
*
|
||||
* CHANGELOG
|
||||
* and additional copyright holders.
|
||||
*
|
||||
* 01/03/2010
|
||||
* Initial version, written by Aleric Inglewood @ SL
|
||||
*/
|
||||
|
||||
#ifndef AISTATEMACHINE_H
|
||||
#define AISTATEMACHINE_H
|
||||
|
||||
#include "aithreadsafe.h"
|
||||
#include "llfasttimer.h"
|
||||
|
||||
//!
|
||||
// A AIStateMachine is a base class that allows derived classes to
|
||||
// go through asynchronous states, while the code still appears to
|
||||
// be more or less sequential.
|
||||
//
|
||||
// These state machine objects can be reused to build more complex
|
||||
// objects.
|
||||
//
|
||||
// It is important to note that each state has a duality: the object
|
||||
// can have a state that will cause a corresponding function to be
|
||||
// called; and often that function will end with changing the state
|
||||
// again, to signal that it was handled. It is easy to confuse the
|
||||
// function of a state with the state at the end of the function.
|
||||
// For example, the state "initialize" could cause the member
|
||||
// function 'init()' to be called, and at the end one would be
|
||||
// inclined to set the state to "initialized". However, this is the
|
||||
// wrong approach: the correct use of state names does reflect the
|
||||
// functions that will be called, never the function that just was
|
||||
// called.
|
||||
//
|
||||
// Each (derived) class goes through a series of states as follows:
|
||||
//
|
||||
// Creation
|
||||
// |
|
||||
// v
|
||||
// (idle) <----. Idle until run() is called.
|
||||
// | |
|
||||
// Initialize | Calls initialize_impl().
|
||||
// | |
|
||||
// | (idle) | Idle until cont() or set_state() is called.
|
||||
// | | ^ |
|
||||
// v v | |
|
||||
// .-------. | |
|
||||
// | Run |_, | Call multiplex_impl() until idle(), abort() or finish() is called.
|
||||
// '-------' |
|
||||
// | | |
|
||||
// v | |
|
||||
// Abort | | Calls abort_impl().
|
||||
// | | |
|
||||
// v v |
|
||||
// Finish | Calls finish_impl().
|
||||
// | |
|
||||
// `---------'
|
||||
//
|
||||
// Each state causes corresponding code to be called.
|
||||
// Finish cleans up whatever is done by Initialize.
|
||||
// Abort should clean up additional things done by Run.
|
||||
//
|
||||
// The Run state is entered by calling run().
|
||||
//
|
||||
// While the base class is in the Run state, it is the derived class
|
||||
// that goes through different states. The state variable of the derived
|
||||
// class is only valid while the base class is in the state Run.
|
||||
//
|
||||
// A derived class can exit the Run state by calling one of two methods:
|
||||
// abort() in case of failure, or finish() in case of success.
|
||||
// Respectively these set the state to Abort and Finish.
|
||||
//
|
||||
// State machines are run from the "idle" part of the viewer main loop.
|
||||
// Often a state machine has nothing to do however. In that case it can
|
||||
// call the method idle(). This will stop the state machine until
|
||||
// external code changes it's state (by calling set_state()), or calls
|
||||
// cont() to continue with the last state.
|
||||
//
|
||||
// The methods of the derived class call set_state() to change their
|
||||
// own state within the bs_run state, or by calling either abort()
|
||||
// or finish().
|
||||
//
|
||||
// Restarting a finished state machine can also be done by calling run(),
|
||||
// which will cause a re-initialize.
|
||||
//
|
||||
// Derived classes should implement the following constants:
|
||||
//
|
||||
// static state_type const min_state = first_state;
|
||||
// static state_type const max_state = last_state + 1;
|
||||
//
|
||||
// Where first_state should be equal to BaseClass::max_state.
|
||||
// These should represent the minimum and (one past) the maximum
|
||||
// values of mRunState.
|
||||
//
|
||||
// virtual void initialize_impl(void)
|
||||
//
|
||||
// Initializes the derived class.
|
||||
//
|
||||
// virtual void multiplex_impl(void);
|
||||
//
|
||||
// This method should handle mRunState in a switch.
|
||||
// For example:
|
||||
//
|
||||
// switch(mRunState)
|
||||
// {
|
||||
// case foo:
|
||||
// handle_foo();
|
||||
// break;
|
||||
// case wait_state:
|
||||
// if (still_waiting())
|
||||
// {
|
||||
// idle();
|
||||
// break;
|
||||
// }
|
||||
// set_state(working);
|
||||
// /*fall-through*/
|
||||
// case working:
|
||||
// do_work();
|
||||
// if (failure())
|
||||
// abort();
|
||||
// break;
|
||||
// case etc:
|
||||
// etc();
|
||||
// finish();
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// virtual void abort_impl(void);
|
||||
//
|
||||
// A call to this method should bring the object to a state
|
||||
// where finish_impl() can be called.
|
||||
//
|
||||
// virtual void finish_impl(void);
|
||||
//
|
||||
// Should cleanup whatever init_impl() did, or any of the
|
||||
// states of the object where multiplex_impl() calls finish().
|
||||
//
|
||||
// virtual char const* state_str_impl(state_type run_state);
|
||||
//
|
||||
// Should return a stringified value of run_state.
|
||||
//
|
||||
class AIStateMachine {
|
||||
//! The type of mState
|
||||
enum base_state_type {
|
||||
bs_initialize,
|
||||
bs_run,
|
||||
bs_abort,
|
||||
bs_finish,
|
||||
bs_deleted
|
||||
};
|
||||
|
||||
public:
|
||||
typedef U32 state_type; //!< The type of mRunState
|
||||
|
||||
//! Integral value equal to the state with the lowest value.
|
||||
static state_type const min_state = bs_initialize;
|
||||
//! Integral value one more than the state with the highest value.
|
||||
static state_type const max_state = bs_deleted + 1;
|
||||
|
||||
private:
|
||||
base_state_type mState; //!< State of the base class.
|
||||
bool mIdle; //!< True if this state machine is not running.
|
||||
bool mAborted; //!< True after calling abort() and before calling run().
|
||||
U64 mSleep; //!< Non-zero while the state machine is sleeping.
|
||||
|
||||
// Callback facilities.
|
||||
// From within an other state machine:
|
||||
AIStateMachine* mParent; //!< The parent object that started this state machine, or NULL if there isn't any.
|
||||
state_type mNewParentState; //!< The state at which the parent should continue upon a successful finish.
|
||||
bool mAbortParent; //!< If true, abort parent on abort(). Otherwise continue as normal.
|
||||
// From outside a state machine:
|
||||
struct callback_type {
|
||||
typedef boost::signal<void (bool)> signal_type;
|
||||
callback_type(signal_type::slot_type const& slot) { connection = signal.connect(slot); }
|
||||
~callback_type() { connection.disconnect(); }
|
||||
void callback(bool success) const { signal(success); }
|
||||
private:
|
||||
boost::signals::connection connection;
|
||||
signal_type signal;
|
||||
};
|
||||
callback_type* mCallback; //!< Pointer to signal/connection, or NULL when not connected.
|
||||
|
||||
static AIThreadSafeSimple<U64> sMaxCount; //!< Number of cpu clocks below which we start a new state machine within the same frame.
|
||||
|
||||
protected:
|
||||
//! State of the derived class. Only valid if mState == bs_run. Call set_state to change.
|
||||
state_type mRunState;
|
||||
|
||||
public:
|
||||
//! Create a non-running state machine.
|
||||
AIStateMachine(void) : mState(bs_initialize), mIdle(true), mAborted(true), mSleep(0), mParent(NULL), mCallback(NULL) { updateSettings(); }
|
||||
|
||||
protected:
|
||||
//! The user should call 'deleteMe()', not delete a AIStateMachine (derived) directly.
|
||||
virtual ~AIStateMachine() { llassert(mState == bs_deleted); }
|
||||
|
||||
public:
|
||||
//! Halt the state machine until cont() is called.
|
||||
void idle(void);
|
||||
|
||||
//! Temporarily halt the state machine.
|
||||
void yield_frame(unsigned int frames) { mSleep = -frames; }
|
||||
|
||||
//! Temporarily halt the state machine.
|
||||
void yield_ms(unsigned int ms) { mSleep = get_cpu_clock_count() + LLFastTimer::countsPerSecond() * ms / 1000; }
|
||||
|
||||
//! Continue running after calling idle.
|
||||
void cont(void);
|
||||
|
||||
//---------------------------------------
|
||||
// Changing the state.
|
||||
|
||||
//! Change state to <code>bs_run</code>. May only be called after creation or after returning from finish().
|
||||
// If <code>parent</code> is non-NULL, change the parent state machine's state to <code>new_parent_state</code>
|
||||
// upon finish, or in the case of an abort and when <code>abort_parent</code> is true, call parent->abort() instead.
|
||||
void run(AIStateMachine* parent, state_type new_parent_state, bool abort_parent = true);
|
||||
|
||||
//! Change state to 'bs_run'. May only be called after creation or after returning from finish().
|
||||
// Does not cause a callback.
|
||||
void run(void) { run(NULL, 0, false); }
|
||||
|
||||
//! The same as above, but pass the result of a boost::bind with _1.
|
||||
//
|
||||
// Here _1, if present, will be replaced with a bool indicating success.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// <code>
|
||||
// struct Foo { void callback(AIStateMachineDerived* ptr, bool success); };
|
||||
// ...
|
||||
// AIStateMachineDerived* magic = new AIStateMachineDerived; // Deleted by callback
|
||||
// // Call foo_ptr->callback(magic, _1) on finish.
|
||||
// state_machine->run(boost::bind(&Foo::callback, foo_ptr, magic, _1));
|
||||
// </code>
|
||||
//
|
||||
// or
|
||||
//
|
||||
// <code>
|
||||
// struct Foo { void callback(bool success, AIStateMachineDerived const& magic); };
|
||||
// ...
|
||||
// AIStateMachineDerived magic;
|
||||
// // Call foo_ptr->callback(_1, magic) on finish.
|
||||
// magic.run(boost::bind(&Foo::callback, foo_ptr, _1, magic));
|
||||
// </code>
|
||||
//
|
||||
// or
|
||||
//
|
||||
// <code>
|
||||
// static void callback(void* userdata);
|
||||
// ...
|
||||
// AIStateMachineDerived magic;
|
||||
// // Call callback(userdata) on finish.
|
||||
// magic.run(boost::bind(&callback, userdata));
|
||||
// </code>
|
||||
void run(callback_type::signal_type::slot_type const& slot);
|
||||
|
||||
//! Change state to 'bs_abort'. May only be called while in the bs_run state.
|
||||
void abort(void);
|
||||
|
||||
//! Change state to 'bs_finish'. May only be called while in the bs_run state.
|
||||
void finish(void);
|
||||
|
||||
//! Refine state while in the bs_run state. May only be called while in the bs_run state.
|
||||
void set_state(state_type run_state);
|
||||
|
||||
//! Change state to 'bs_deleted'. May only be called while in the bs_finish state.
|
||||
void deleteMe(void);
|
||||
|
||||
//---------------------------------------
|
||||
// Other.
|
||||
|
||||
//! Called whenever the StateMachineMaxTime setting is changed.
|
||||
static void updateSettings(void);
|
||||
|
||||
//---------------------------------------
|
||||
// Accessors.
|
||||
|
||||
//! Return true if state machine was aborted (can be used in finish_impl).
|
||||
bool aborted(void) const { return mAborted; }
|
||||
|
||||
//! Return true if the derived class is running (also when we are idle).
|
||||
bool running(void) const { return mState == bs_run; }
|
||||
|
||||
//! Return true if the derived class is running but idle.
|
||||
bool waiting(void) const { return mState == bs_run && mIdle; }
|
||||
|
||||
// Use some safebool idiom (http://www.artima.com/cppsource/safebool.html) rather than operator bool.
|
||||
typedef state_type AIStateMachine::* const bool_type;
|
||||
//! Return true if state machine successfully finished.
|
||||
operator bool_type() const { return (mState == bs_initialize && !mAborted) ? &AIStateMachine::mRunState : 0; }
|
||||
|
||||
//! Return a stringified state, for debugging purposes.
|
||||
char const* state_str(state_type state);
|
||||
|
||||
private:
|
||||
static void mainloop(void*);
|
||||
void multiplex(U64 current_time);
|
||||
|
||||
protected:
|
||||
//---------------------------------------
|
||||
// Derived class implementations.
|
||||
|
||||
// Handle initializing the object.
|
||||
virtual void initialize_impl(void) = 0;
|
||||
|
||||
// Handle mRunState.
|
||||
virtual void multiplex_impl(void) = 0;
|
||||
|
||||
// Handle aborting from current bs_run state.
|
||||
virtual void abort_impl(void) = 0;
|
||||
|
||||
// Handle cleaning up from initialization (or post abort) state.
|
||||
virtual void finish_impl(void) = 0;
|
||||
|
||||
// Implemenation of state_str for run states.
|
||||
virtual char const* state_str_impl(state_type run_state) const = 0;
|
||||
};
|
||||
|
||||
// This case be used in state_str_impl.
|
||||
#define AI_CASE_RETURN(x) do { case x: return #x; } while(0)
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user