477 lines
17 KiB
C++
477 lines
17 KiB
C++
/**
|
|
* @file aistatemachine.h
|
|
* @brief State machine base class
|
|
*
|
|
* Copyright (c) 2010 - 2013, 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
|
|
*
|
|
* 28/02/2013
|
|
* Rewritten from scratch to fully support threading.
|
|
*/
|
|
|
|
#ifndef AISTATEMACHINE_H
|
|
#define AISTATEMACHINE_H
|
|
|
|
#include "aithreadsafe.h"
|
|
#include <llpointer.h>
|
|
#include "lltimer.h"
|
|
#include <list>
|
|
#include <boost/signals2.hpp>
|
|
|
|
class AIConditionBase;
|
|
class AIStateMachine;
|
|
|
|
class AIEngine
|
|
{
|
|
private:
|
|
struct QueueElementComp;
|
|
class QueueElement {
|
|
private:
|
|
LLPointer<AIStateMachine> mStateMachine;
|
|
|
|
public:
|
|
QueueElement(AIStateMachine* statemachine) : mStateMachine(statemachine) { }
|
|
friend bool operator==(QueueElement const& e1, QueueElement const& e2) { return e1.mStateMachine == e2.mStateMachine; }
|
|
friend bool operator!=(QueueElement const& e1, QueueElement const& e2) { return e1.mStateMachine != e2.mStateMachine; }
|
|
friend struct QueueElementComp;
|
|
|
|
AIStateMachine const& statemachine(void) const { return *mStateMachine; }
|
|
AIStateMachine& statemachine(void) { return *mStateMachine; }
|
|
};
|
|
struct QueueElementComp {
|
|
inline bool operator()(QueueElement const& e1, QueueElement const& e2) const;
|
|
};
|
|
|
|
public:
|
|
typedef std::list<QueueElement> queued_type;
|
|
struct engine_state_type {
|
|
queued_type list;
|
|
bool waiting;
|
|
engine_state_type(void) : waiting(false) { }
|
|
};
|
|
|
|
private:
|
|
AIThreadSafeSimpleDC<engine_state_type, LLCondition> mEngineState;
|
|
typedef AIAccessConst<engine_state_type, LLCondition> engine_state_type_crat;
|
|
typedef AIAccess<engine_state_type, LLCondition> engine_state_type_rat;
|
|
typedef AIAccess<engine_state_type, LLCondition> engine_state_type_wat;
|
|
char const* mName;
|
|
|
|
static U64 sMaxCount;
|
|
|
|
public:
|
|
AIEngine(char const* name) : mName(name) { }
|
|
|
|
void add(AIStateMachine* state_machine);
|
|
|
|
void mainloop(void);
|
|
void threadloop(void);
|
|
void wake_up(void);
|
|
void flush(void);
|
|
|
|
char const* name(void) const { return mName; }
|
|
|
|
static void setMaxCount(F32 StateMachineMaxTime);
|
|
};
|
|
|
|
extern AIEngine gMainThreadEngine;
|
|
extern AIEngine gStateMachineThreadEngine;
|
|
|
|
#ifndef STATE_MACHINE_PROFILING
|
|
#ifndef LL_RELEASE_FOR_DOWNLOAD
|
|
#define STATE_MACHINE_PROFILING 1
|
|
#endif
|
|
#endif
|
|
|
|
class AIStateMachine : public LLThreadSafeRefCount
|
|
{
|
|
public:
|
|
typedef U32 state_type; //!< The type of run_state
|
|
|
|
// A simple timer class that will calculate time delta between ctor and GetTimerData call.
|
|
// Time data is stored as a nested TimeData object.
|
|
// If STATE_MACHINE_PROFILING is defined then a stack of all StateTimers from root is maintained for debug output.
|
|
class StateTimerBase
|
|
{
|
|
public:
|
|
class TimeData
|
|
{
|
|
friend class StateTimerBase;
|
|
public:
|
|
TimeData() : mStart(-1), mEnd(-1) {}
|
|
U64 GetDuration() { return mEnd - mStart; }
|
|
private:
|
|
U64 mStart, mEnd;
|
|
|
|
#if !STATE_MACHINE_PROFILING
|
|
TimeData(const std::string& name) : mStart(get_clock_count()), mEnd(get_clock_count()) {}
|
|
#else
|
|
TimeData(const std::string& name) : mName(name), mStart(get_clock_count()), mEnd(get_clock_count()) {}
|
|
void DumpTimer(std::ostringstream& msg, std::string prefix);
|
|
std::vector<TimeData> mChildren;
|
|
std::string mName;
|
|
static TimeData sRoot;
|
|
#endif
|
|
};
|
|
#if !STATE_MACHINE_PROFILING
|
|
StateTimerBase(const std::string& name) : mData(name) {}
|
|
~StateTimerBase() {}
|
|
protected:
|
|
TimeData mData;
|
|
// Return a copy of the underlying timer data.
|
|
// This allows the data live beyond the scope of the state timer.
|
|
public:
|
|
const TimeData GetTimerData()
|
|
{
|
|
mData.mEnd = get_clock_count(); //set mEnd to current time, since GetTimerData() will always be called before the dtor, obv.
|
|
return mData;
|
|
}
|
|
#else
|
|
protected:
|
|
// Ctors/dtors are hidden. Only StateTimerRoot and StateTimer are permitted to access them.
|
|
StateTimerBase() : mData(NULL) {}
|
|
~StateTimerBase()
|
|
{
|
|
// If mData is null then the timer was not registered due to being in the wrong thread or the root timer wasn't in the expected state.
|
|
if (!mData)
|
|
return;
|
|
mData->mEnd = get_clock_count();
|
|
mTimerStack.pop_back();
|
|
}
|
|
|
|
// Also hide internals from everything except StateTimerRoot and StateTimer
|
|
bool AddAsRoot(const std::string& name)
|
|
{
|
|
|
|
if (!is_main_thread())
|
|
return true; //Ignoring this timer, but pretending it was added.
|
|
if (!mTimerStack.empty())
|
|
return false;
|
|
TimeData::sRoot = TimeData(name);
|
|
mData = &TimeData::sRoot;
|
|
mData->mChildren.clear();
|
|
mTimerStack.push_back(this);
|
|
return true;
|
|
}
|
|
bool AddAsChild(const std::string& name)
|
|
{
|
|
if (!is_main_thread())
|
|
return true; //Ignoring this timer, but pretending it was added.
|
|
if (mTimerStack.empty())
|
|
return false;
|
|
mTimerStack.back()->mData->mChildren.push_back(TimeData(name));
|
|
mData = &mTimerStack.back()->mData->mChildren.back();
|
|
mTimerStack.push_back(this);
|
|
return true;
|
|
}
|
|
|
|
TimeData* mData;
|
|
static std::vector<StateTimerBase*> mTimerStack;
|
|
|
|
public:
|
|
// Debug spew
|
|
static void DumpTimers(std::ostringstream& msg)
|
|
{
|
|
TimeData::sRoot.DumpTimer(msg, "");
|
|
}
|
|
|
|
// Return a copy of the underlying timer data.
|
|
// This allows the data live beyond the scope of the state timer.
|
|
const TimeData GetTimerData() const
|
|
{
|
|
if (mData)
|
|
{
|
|
TimeData ret = *mData;
|
|
ret.mEnd = get_clock_count(); //set mEnd to current time, since GetTimerData() will always be called before the dtor, obv.
|
|
return ret;
|
|
}
|
|
return TimeData();
|
|
}
|
|
#endif
|
|
};
|
|
public:
|
|
#if !STATE_MACHINE_PROFILING
|
|
typedef StateTimerBase StateTimerRoot;
|
|
typedef StateTimerBase StateTimer;
|
|
#else
|
|
class StateTimerRoot : public StateTimerBase
|
|
{ //A StateTimerRoot can become a child if a root already exists.
|
|
public:
|
|
StateTimerRoot(const std::string& name)
|
|
{
|
|
if(!AddAsRoot(name))
|
|
AddAsChild(name);
|
|
}
|
|
};
|
|
class StateTimer : public StateTimerBase
|
|
{ //A StateTimer can never become a root
|
|
public:
|
|
StateTimer(const std::string& name)
|
|
{
|
|
AddAsChild(name);
|
|
}
|
|
};
|
|
#endif
|
|
|
|
|
|
protected:
|
|
// The type of event that causes multiplex() to be called.
|
|
enum event_type {
|
|
initial_run,
|
|
schedule_run,
|
|
normal_run,
|
|
insert_abort
|
|
};
|
|
// The type of mState
|
|
enum base_state_type {
|
|
bs_reset, // Idle state before run() is called. Reference count is zero (except for a possible external LLPointer).
|
|
bs_initialize, // State after run() and before/during initialize_impl().
|
|
bs_multiplex, // State after initialize_impl() before finish() or abort().
|
|
bs_abort,
|
|
bs_finish,
|
|
bs_callback,
|
|
bs_killed
|
|
};
|
|
public:
|
|
static state_type const max_state = bs_killed + 1;
|
|
|
|
protected:
|
|
struct multiplex_state_type {
|
|
base_state_type base_state;
|
|
AIEngine* current_engine; // Current engine.
|
|
multiplex_state_type(void) : base_state(bs_reset), current_engine(NULL) { }
|
|
};
|
|
struct sub_state_type {
|
|
state_type run_state;
|
|
state_type advance_state;
|
|
AIConditionBase* blocked;
|
|
bool reset;
|
|
bool need_run;
|
|
bool idle;
|
|
bool skip_idle;
|
|
bool aborted;
|
|
bool finished;
|
|
};
|
|
|
|
private:
|
|
// Base state.
|
|
AIThreadSafeSimpleDC<multiplex_state_type> mState;
|
|
typedef AIAccessConst<multiplex_state_type> multiplex_state_type_crat;
|
|
typedef AIAccess<multiplex_state_type> multiplex_state_type_rat;
|
|
typedef AIAccess<multiplex_state_type> multiplex_state_type_wat;
|
|
|
|
protected:
|
|
// Sub state.
|
|
AIThreadSafeSimpleDC<sub_state_type> mSubState;
|
|
typedef AIAccessConst<sub_state_type> sub_state_type_crat;
|
|
typedef AIAccess<sub_state_type> sub_state_type_rat;
|
|
typedef AIAccess<sub_state_type> sub_state_type_wat;
|
|
|
|
private:
|
|
// Mutex protecting everything below and making sure only one thread runs the state machine at a time.
|
|
LLMutex mMultiplexMutex;
|
|
// Mutex that is locked while calling *_impl() functions and the call back.
|
|
LLMutex mRunMutex;
|
|
|
|
S64 mSleep; //!< Non-zero while the state machine is sleeping.
|
|
|
|
// Callback facilities.
|
|
// From within an other state machine:
|
|
LLPointer<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.
|
|
bool mOnAbortSignalParent; // If true and mAbortParent is false, change state of parent even on abort.
|
|
// From outside a state machine:
|
|
struct callback_type {
|
|
typedef boost::signals2::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::signals2::connection connection;
|
|
signal_type signal;
|
|
};
|
|
callback_type* mCallback; // Pointer to signal/connection, or NULL when not connected.
|
|
|
|
// Engine stuff.
|
|
AIEngine* mDefaultEngine; // Default engine.
|
|
AIEngine* mYieldEngine; // Requested engine.
|
|
|
|
#ifdef SHOW_ASSERT
|
|
// Debug stuff.
|
|
AIThreadID mThreadId; // The thread currently running multiplex().
|
|
base_state_type mDebugLastState; // The previous state that multiplex() had a normal run with.
|
|
bool mDebugShouldRun; // Set if we found evidence that we should indeed call multiplex_impl().
|
|
bool mDebugAborted; // True when abort() was called.
|
|
bool mDebugContPending; // True while cont() was called by not handled yet.
|
|
bool mDebugSetStatePending; // True while set_state() was called by not handled yet.
|
|
bool mDebugAdvanceStatePending; // True while advance_state() was called by not handled yet.
|
|
bool mDebugRefCalled; // True when ref() is called (or will be called within the critial area of mMultiplexMutex).
|
|
#endif
|
|
#ifdef CWDEBUG
|
|
protected:
|
|
bool mSMDebug; // Print debug output only when true.
|
|
#endif
|
|
private:
|
|
U64 mRuntime; // Total time spent running in the main thread (in clocks).
|
|
|
|
public:
|
|
AIStateMachine(CWD_ONLY(bool debug)) : mCallback(NULL), mDefaultEngine(NULL), mYieldEngine(NULL),
|
|
#ifdef SHOW_ASSERT
|
|
mThreadId(AIThreadID::none), mDebugLastState(bs_killed), mDebugShouldRun(false), mDebugAborted(false), mDebugContPending(false),
|
|
mDebugSetStatePending(false), mDebugAdvanceStatePending(false), mDebugRefCalled(false),
|
|
#endif
|
|
#ifdef CWDEBUG
|
|
mSMDebug(debug),
|
|
#endif
|
|
mRuntime(0)
|
|
{ }
|
|
|
|
protected:
|
|
// The user should call finish() (or abort(), or kill() from the call back when finish_impl() calls run()),
|
|
// not delete a class derived from AIStateMachine directly. Deleting it directly before calling run() is
|
|
// ok however.
|
|
virtual ~AIStateMachine()
|
|
{
|
|
#ifdef SHOW_ASSERT
|
|
base_state_type state = multiplex_state_type_rat(mState)->base_state;
|
|
llassert(state == bs_killed || state == bs_reset);
|
|
#endif
|
|
}
|
|
|
|
public:
|
|
// These functions may be called directly after creation, or from within finish_impl(), or from the call back function.
|
|
void run(AIStateMachine* parent, state_type new_parent_state, bool abort_parent = true, bool on_abort_signal_parent = true, AIEngine* default_engine = &gMainThreadEngine);
|
|
void run(callback_type::signal_type::slot_type const& slot, AIEngine* default_engine = &gMainThreadEngine);
|
|
void run(void) { run(NULL, 0, false, true, mDefaultEngine); }
|
|
|
|
// This function may only be called from the call back function (and cancels a call to run() from finish_impl()).
|
|
void kill(void);
|
|
|
|
protected:
|
|
// This function can be called from initialize_impl() and multiplex_impl() (both called from within multiplex()).
|
|
void set_state(state_type new_state); // Run this state the NEXT loop.
|
|
// These functions can only be called from within multiplex_impl().
|
|
void idle(void); // Go idle unless cont() or advance_state() were called since the start of the current loop, or until they are called.
|
|
void wait(AIConditionBase& condition); // The same as idle(), but wake up when AICondition<T>::signal() is called.
|
|
void finish(void); // Mark that the state machine finished and schedule the call back.
|
|
void yield(void); // Yield to give CPU to other state machines, but do not go idle.
|
|
void yield(AIEngine* engine); // Yield to give CPU to other state machines, but do not go idle. Continue running from engine 'engine'.
|
|
void yield_frame(unsigned int frames); // Run from the main-thread engine after at least 'frames' frames have passed.
|
|
void yield_ms(unsigned int ms); // Run from the main-thread engine after roughly 'ms' miliseconds have passed.
|
|
bool yield_if_not(AIEngine* engine); // Do not really yield, unless the current engine is not 'engine'. Returns true if it switched engine.
|
|
|
|
public:
|
|
// This function can be called from multiplex_imp(), but also by a child state machine and
|
|
// therefore by any thread. The child state machine should use an LLPointer<AIStateMachine>
|
|
// to access this state machine.
|
|
void abort(void); // Abort the state machine (unsuccessful finish).
|
|
|
|
// These are the only three functions that can be called by any thread at any moment.
|
|
// Those threads should use an LLPointer<AIStateMachine> to access this state machine.
|
|
void cont(void); // Guarantee at least one full run of multiplex() after this function is called. Cancels the last call to idle().
|
|
void advance_state(state_type new_state); // Guarantee at least one full run of multiplex() after this function is called
|
|
// iff new_state is larger than the last state that was processed.
|
|
bool signalled(void); // Call cont() iff this state machine is still blocked after a call to wait(). Returns false if it already unblocked.
|
|
|
|
public:
|
|
// Accessors.
|
|
|
|
// Return true if the derived class is running (also when we are idle).
|
|
bool running(void) const { return multiplex_state_type_crat(mState)->base_state == bs_multiplex; }
|
|
// Return true if the derived class is running and idle.
|
|
bool waiting(void) const
|
|
{
|
|
multiplex_state_type_crat state_r(mState);
|
|
return state_r->base_state == bs_multiplex && sub_state_type_crat(mSubState)->idle;
|
|
}
|
|
// Return true if the derived class is running and idle or already being aborted.
|
|
bool waiting_or_aborting(void) const
|
|
{
|
|
multiplex_state_type_crat state_r(mState);
|
|
return state_r->base_state == bs_abort || ( state_r->base_state == bs_multiplex && sub_state_type_crat(mSubState)->idle);
|
|
}
|
|
// Return true if are added to the engine.
|
|
bool active(AIEngine const* engine) const { return multiplex_state_type_crat(mState)->current_engine == engine; }
|
|
bool aborted(void) const { return sub_state_type_crat(mSubState)->aborted; }
|
|
|
|
// 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
|
|
{
|
|
sub_state_type_crat sub_state_r(mSubState);
|
|
return (sub_state_r->finished && !sub_state_r->aborted) ? &AIStateMachine::mNewParentState : 0;
|
|
}
|
|
|
|
// Return stringified state, for debugging purposes.
|
|
char const* state_str(base_state_type state);
|
|
#ifdef CWDEBUG
|
|
char const* event_str(event_type event);
|
|
#endif
|
|
|
|
void add(U64 count) { mRuntime += count; }
|
|
U64 getRuntime(void) const { return mRuntime; }
|
|
|
|
// For diagnostics. Every derived class must override this.
|
|
virtual const char* getName() const = 0;
|
|
|
|
protected:
|
|
virtual void initialize_impl(void) = 0;
|
|
virtual void multiplex_impl(state_type run_state) = 0;
|
|
virtual void abort_impl(void) { }
|
|
virtual void finish_impl(void) { }
|
|
virtual char const* state_str_impl(state_type run_state) const = 0;
|
|
virtual void force_killed(void); // Called from AIEngine::flush().
|
|
|
|
private:
|
|
void reset(void); // Called from run() to (re)initialize a (re)start.
|
|
void multiplex(event_type event); // Called from AIEngine to step through the states (and from reset() to kick start the state machine).
|
|
state_type begin_loop(base_state_type base_state); // Called from multiplex() at the start of a loop.
|
|
void callback(void); // Called when the state machine finished.
|
|
bool sleep(U64 current_time) // Count frames if necessary and return true when the state machine is still sleeping.
|
|
{
|
|
if (mSleep == 0)
|
|
return false;
|
|
else if (mSleep < 0)
|
|
++mSleep;
|
|
else if ((U64)mSleep <= current_time)
|
|
mSleep = 0;
|
|
return mSleep != 0;
|
|
}
|
|
|
|
friend class AIEngine; // Calls multiplex() and force_killed().
|
|
};
|
|
|
|
bool AIEngine::QueueElementComp::operator()(QueueElement const& e1, QueueElement const& e2) const
|
|
{
|
|
return e1.mStateMachine->getRuntime() < e2.mStateMachine->getRuntime();
|
|
}
|
|
|
|
// This can be used in state_str_impl.
|
|
#define AI_CASE_RETURN(x) do { case x: return #x; } while(0)
|
|
|
|
#endif
|