Files
SingularityViewer/indra/aistatemachine/aistatemachine.cpp
Aleric Inglewood 09708b6318 Added AICondition.
AICondition is like AIThreadSafeSimpleDC (it is derived from it),
and wraps some variable, protecting it with a builtin mutex.

While in a statemachine, one can call wait(condition) to make
the state machine go idle, and call condition.signal() to
wake it (or another) up again.

While normally a condition variable is used as follows:

condition.lock();
while (!whatwerewaitingfor)
{
  condition.wait();
}
// Here the condition is guaranteed to be true and we're
// still in the critical area of the mutex.
condition.unlock();

where the thread blocks in wait(), the statemachine actually
returns CPU to the thread (the AIEngine), so there is no
while loop involved, and our wait() doesn't even unlock
the mutex: that happens because the state machine returns:

condition_wat condition_w(condition);		// Lock condition.
if (!condition_w->whatwerewaitingfor())		// The variable(s) involved can only be accessed when condition is locked.
{
  wait(condition);				// Causes the state machine to go idle. This does not block.
  break;					// Leave scope (unlock condition) and return to the mainloop - but as idle state machine.
}
// Here the condition is guaranteed to be true and we're
// still in the critical area of the condition variable.

In this case, when condition.signal() is called, the thread
doesn't return from wait() with a locked mutex - but the
statemachine executes the same state again, and enters from
the top: locking the condition and doing the test again,
just like it would be when this was a while loop.
2013-10-15 22:30:53 +02:00

1415 lines
48 KiB
C++

/**
* @file aistatemachine.cpp
* @brief Implementation of AIStateMachine
*
* 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.
*/
#include "linden_common.h"
#include "aistatemachine.h"
#include "aicondition.h"
#include "lltimer.h"
//==================================================================
// Overview
// 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 advance_state() is called.
// | | ^ |
// v v | |
// .-----------. |
// | Multiplex | | Call multiplex_impl() until idle(), abort() or finish() is called.
// '-----------' |
// | | |
// v | |
// Abort | | Calls abort_impl().
// | | |
// v v |
// Finish | Calls finish_impl(), which may call run().
// | | |
// | v |
// | Callback | which may call kill() and/or run().
// | | | |
// | | `-----'
// v v
// Killed Delete the statemachine (all statemachines must be allocated with new).
//
// Each state causes corresponding code to be called.
// Finish cleans up whatever is done by Initialize.
// Abort should clean up additional things done while running.
//
// The running state is entered by calling run().
//
// While the base class is in the bs_multiplex 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 bs_multiplex state.
//
// A derived class can exit the bs_multiplex state by calling one of two methods:
// abort() in case of failure, or finish() in case of success.
// Respectively these set the state to bs_abort and bs_finish.
//
// The methods of the derived class call set_state() to change their
// own state within the bs_multiplex state, or by calling either abort()
// or finish().
//
// Restarting a finished state machine can be done by calling run(),
// which will cause a re-initialize. The default is to destruct the
// state machine once the last LLPointer to it is deleted.
//
//==================================================================
// Declaration
// Every state machine is (indirectly) derived from AIStateMachine.
// For example:
#ifdef EXAMPLE_CODE // undefined
class HelloWorld : public AIStateMachine {
protected:
// The base class of this state machine.
typedef AIStateMachine direct_base_type;
// The different states of the state machine.
enum hello_world_state_type {
HelloWorld_start = direct_base_type::max_state,
HelloWorld_done,
};
public:
static state_type const max_state = HelloWorld_done + 1; // One beyond the largest state.
public:
// The derived class must have a default constructor.
HelloWorld();
protected:
// The destructor must be protected.
/*virtual*/ ~HelloWorld();
protected:
// The following virtual functions must be implemented:
// Handle initializing the object.
/*virtual*/ void initialize_impl(void);
// Handle mRunState.
/*virtual*/ void multiplex_impl(state_type run_state);
// Handle aborting from current bs_multiplex state (the default AIStateMachine::abort_impl() does nothing).
/*virtual*/ void abort_impl(void);
// Handle cleaning up from initialization (or post abort) state (the default AIStateMachine::finish_impl() does nothing).
/*virtual*/ void finish_impl(void);
// Return human readable string for run_state.
/*virtual*/ char const* state_str_impl(state_type run_state) const;
};
// In the .cpp file:
char const* HelloWorld::state_str_impl(state_type run_state) const
{
switch(run_state)
{
// A complete listing of hello_world_state_type.
AI_CASE_RETURN(HelloWorld_start);
AI_CASE_RETURN(HelloWorld_done);
}
#if directly_derived_from_AIStateMachine
llassert(false);
return "UNKNOWN STATE";
#else
llassert(run_state < direct_base_type::max_state);
return direct_base_type::state_str_impl(run_state);
#endif
}
#endif // EXAMPLE_CODE
//==================================================================
// Life cycle: creation, initialization, running and destruction
// Any thread may create a state machine object, initialize it by calling
// it's initializing member function and call one of the 'run' methods,
// which might or might not immediately start to execute the state machine.
#ifdef EXAMPLE_CODE
HelloWorld* hello_world = new HelloWorld;
hello_world->init(...); // A custom initialization function.
hello_world->run(...); // One of the run() functions.
// hello_world might be destructed here.
// You can avoid possible destruction by using an LLPointer<HelloWorld>
// instead of HelloWorld*.
#endif // EXAMPLE_CODE
// The call to run() causes a call to initialize_impl(), which MUST call
// set_state() at least once (only the last call is used).
// Upon return from initialize_impl(), multiplex_impl() will be called
// with that state.
// multiplex_impl() may never reentrant (cause itself to be called).
// multiplex_impl() should end by callling either one of:
// idle(), yield*(), finish() [or abort()].
// Leaving multiplex_impl() without calling any of those might result in an
// immediate reentry, which could lead to 100% CPU usage unless the state
// is changed with set_state().
// If multiplex_impl() calls finish() then finish_impl() will be called [if it
// calls abort() then abort_impl() will called, followed by finish_impl()].
// Upon return from multiplex_impl(), and if finish() [or abort()] was called,
// the call back passed to run() will be called.
// Upon return from the call back, the state machine object might be destructed
// (see below).
// If idle() was called, and the state was (still) current_state,
// then multiplex_impl() will not be called again until the state is
// advanced, or cont() is called.
//
// If the call back function does not call run(), then the state machine is
// deleted when the last LLPointer<> reference is deleted.
// If kill() is called after run() was called, then the call to run() is ignored.
//==================================================================
// Aborting
// If abort() is called before initialize_impl() is entered, then the state
// machine is destructed after the last LLPointer<> reference to it is
// deleted (if any). Note that this is only possible when a child state
// machine is aborted before the parent even runs.
//
// If abort() is called inside its initialize_impl() that initialize_impl()
// should return immediately after.
// if idle(), abort() or finish() are called inside its multiplex_impl() then
// that multiplex_impl() should return immediately after.
//
//==================================================================
// Thread safety
// Only one thread can "run" a state machine at a time; can call 'multiplex_impl'.
//
// Only from inside multiplex_impl (set_state also from initialize_impl), any of the
// following functions can be called:
//
// - set_state(new_state) --> Force the state to new_state. This voids any previous call to set_state() or idle().
// - idle() --> If there was no call to advance_state() since the last call to set_state(current_state))
// then go idle (do nothing until cont() or advance_state() is called). If the current
// state is not current_state, then multiplex_impl shall be reentered immediately upon return.
// - finish() --> Disables any scheduled runs.
// --> finish_impl --> [optional] kill()
// --> call back
// --> [optional] delete
// --> [optional] reset, upon return from multiplex_impl, call initialize_impl and start again at the top of multiplex.
// - yield([engine]) --> give CPU to other state machines before running again, run next from a state machine engine.
// If no engine is passed, the state machine will run in it's default engine (as set during construction).
// - yield_frame()/yield_ms() --> yield(&gMainThreadEngine)
//
// the following function may be called from multiplex_impl() of any state machine (and thus by any thread):
//
// - abort() --> abort_impl
// --> finish()
//
// while the following functions may be called from anywhere (and any thread):
//
// - cont() --> schedules a run if there was no call to set_state() or advance_state() since the last call to idle().
// - advance_state(new_state) --> sets the state to new_state, if the new_state > current_state, and schedules a run (and voids the last call to idle()).
//
// In the above "scheduling a run" means calling multiplex_impl(), but the same holds for any *_impl()
// and the call back: Whenever one of those have to be called, thread_safe_impl() is called to
// determine if the current state machine allows that function to be called by the current thread,
// and if not - by which thread it should be called then (either main thread, or a special state machine
// thread). If thread switching is necessary, the call is literally scheduled in a queue of one
// of those two, otherwise it is run immediately.
//
// However, since only one thread at a time may be calling any *_impl function (except thread_safe_impl())
// or the call back function, it is possible that at the moment scheduling is necessary another thread
// is already running one of those functions. In that case thread_safe_impl() does not consider the
// current thread, but rather the running thread and does not do any scheduling if the running thread
// is ok, rather marks the need to continue running which should be picked up upon return from
// whatever the running thread is calling.
void AIEngine::add(AIStateMachine* state_machine)
{
Dout(dc::statemachine(state_machine->mSMDebug), "Adding state machine [" << (void*)state_machine << "] to " << mName);
engine_state_type_wat engine_state_w(mEngineState);
engine_state_w->list.push_back(QueueElement(state_machine));
if (engine_state_w->waiting)
{
engine_state_w.signal();
}
}
extern void print_statemachine_diagnostics(U64 total_clocks, U64 max_delta, AIEngine::queued_type::const_reference slowest_state_machine);
// MAIN-THREAD
void AIEngine::mainloop(void)
{
queued_type::iterator queued_element, end;
{
engine_state_type_wat engine_state_w(mEngineState);
end = engine_state_w->list.end();
queued_element = engine_state_w->list.begin();
}
U64 total_clocks = 0;
#ifndef LL_RELEASE_FOR_DOWNLOAD
U64 max_delta = 0;
queued_type::value_type slowest_element(NULL);
#endif
while (queued_element != end)
{
AIStateMachine& state_machine(queued_element->statemachine());
U64 start = get_clock_count();
if (!state_machine.sleep(start))
{
state_machine.multiplex(AIStateMachine::normal_run);
}
U64 delta = get_clock_count() - start;
state_machine.add(delta);
total_clocks += delta;
#ifndef LL_RELEASE_FOR_DOWNLOAD
if (delta > max_delta)
{
max_delta = delta;
slowest_element = *queued_element;
}
#endif
bool active = state_machine.active(this); // This locks mState shortly, so it must be called before locking mEngineState because add() locks mEngineState while holding mState.
engine_state_type_wat engine_state_w(mEngineState);
if (!active)
{
Dout(dc::statemachine(state_machine.mSMDebug), "Erasing state machine [" << (void*)&state_machine << "] from " << mName);
engine_state_w->list.erase(queued_element++);
}
else
{
++queued_element;
}
if (total_clocks >= sMaxCount)
{
#ifndef LL_RELEASE_FOR_DOWNLOAD
print_statemachine_diagnostics(total_clocks, max_delta, slowest_element);
#endif
Dout(dc::statemachine, "Sorting " << engine_state_w->list.size() << " state machines.");
engine_state_w->list.sort(QueueElementComp());
break;
}
}
}
void AIEngine::flush(void)
{
engine_state_type_wat engine_state_w(mEngineState);
DoutEntering(dc::statemachine, "AIEngine::flush [" << mName << "]: calling force_killed() on " << engine_state_w->list.size() << " state machines.");
for (queued_type::iterator iter = engine_state_w->list.begin(); iter != engine_state_w->list.end(); ++iter)
{
// To avoid an assertion in ~AIStateMachine.
iter->statemachine().force_killed();
}
engine_state_w->list.clear();
}
// static
U64 AIEngine::sMaxCount;
// static
void AIEngine::setMaxCount(F32 StateMachineMaxTime)
{
llassert(AIThreadID::in_main_thread());
Dout(dc::statemachine, "(Re)calculating AIStateMachine::sMaxCount");
sMaxCount = calc_clock_frequency() * StateMachineMaxTime / 1000;
}
#if defined(CWDEBUG) || defined(DEBUG_CURLIO)
char const* AIStateMachine::event_str(event_type event)
{
switch(event)
{
AI_CASE_RETURN(initial_run);
AI_CASE_RETURN(schedule_run);
AI_CASE_RETURN(normal_run);
AI_CASE_RETURN(insert_abort);
}
llassert(false);
return "UNKNOWN EVENT";
}
#endif
void AIStateMachine::multiplex(event_type event)
{
// If this fails then you are using a pointer to a state machine instead of an LLPointer.
llassert(event == initial_run || getNumRefs() > 0);
DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::multiplex(" << event_str(event) << ") [" << (void*)this << "]");
base_state_type state;
state_type run_state;
// Critical area of mState.
{
multiplex_state_type_rat state_r(mState);
// If another thread is already running multiplex() then it will pick up
// our need to run (by us having set need_run), so there is no need to run
// ourselves.
llassert(!mMultiplexMutex.isSelfLocked()); // We may never enter recursively!
if (!mMultiplexMutex.tryLock())
{
Dout(dc::statemachine(mSMDebug), "Leaving because it is already being run [" << (void*)this << "]");
return;
}
//===========================================
// Start of critical area of mMultiplexMutex.
// If another thread already called begin_loop() since we needed a run,
// then we must not schedule a run because that could lead to running
// the same state twice. Note that if need_run was reset in the mean
// time and then set again, then it can't hurt to schedule a run since
// we should indeed run, again.
if (event == schedule_run && !sub_state_type_rat(mSubState)->need_run)
{
Dout(dc::statemachine(mSMDebug), "Leaving because it was already being run [" << (void*)this << "]");
return;
}
// We're at the beginning of multiplex, about to actually run it.
// Make a copy of the states.
run_state = begin_loop((state = state_r->base_state));
}
// End of critical area of mState.
bool keep_looping;
bool destruct = false;
do
{
if (event == normal_run)
{
#ifdef CWDEBUG
if (state == bs_multiplex)
Dout(dc::statemachine(mSMDebug), "Running state bs_multiplex / " << state_str_impl(run_state) << " [" << (void*)this << "]");
else
Dout(dc::statemachine(mSMDebug), "Running state " << state_str(state) << " [" << (void*)this << "]");
#endif
#ifdef SHOW_ASSERT
// This debug code checks that each state machine steps precisely through each of it's states correctly.
if (state != bs_reset)
{
switch(mDebugLastState)
{
case bs_reset:
llassert(state == bs_initialize || state == bs_killed);
break;
case bs_initialize:
llassert(state == bs_multiplex || state == bs_abort);
break;
case bs_multiplex:
llassert(state == bs_multiplex || state == bs_finish || state == bs_abort);
break;
case bs_abort:
llassert(state == bs_finish);
break;
case bs_finish:
llassert(state == bs_callback);
break;
case bs_callback:
llassert(state == bs_killed || state == bs_reset);
break;
case bs_killed:
llassert(state == bs_killed);
break;
}
}
// More sanity checks.
if (state == bs_multiplex)
{
// set_state is only called from multiplex_impl and therefore synced with mMultiplexMutex.
mDebugShouldRun |= mDebugSetStatePending;
// Should we run at all?
llassert(mDebugShouldRun);
}
// Any previous reason to run is voided by actually running.
mDebugShouldRun = false;
#endif
mRunMutex.lock();
// Now we are actually running a single state.
// If abort() was called at any moment before, we execute that state instead.
bool const late_abort = (state == bs_multiplex || state == bs_initialize) && sub_state_type_rat(mSubState)->aborted;
if (LL_UNLIKELY(late_abort))
{
// abort() was called from a child state machine, from another thread, while we were already scheduled to run normally from an engine.
// What we want to do here is pretend we detected the abort at the end of the *previous* run.
// If the state is bs_multiplex then the previous state was either bs_initialize or bs_multiplex,
// both of which would have switched to bs_abort: we set the state to bs_abort instead and just
// continue this run.
// However, if the state is bs_initialize we can't switch to bs_killed because that state isn't
// handled in the switch below; it's only handled when exiting multiplex() directly after it is set.
// Therefore, in that case we have to set the state BACK to bs_reset and run it again. This duplicated
// run of bs_reset is not a problem because it happens to be a NoOp.
state = (state == bs_initialize) ? bs_reset : bs_abort;
#ifdef CWDEBUG
Dout(dc::statemachine(mSMDebug), "Late abort detected! Running state " << state_str(state) << " instead [" << (void*)this << "]");
#endif
}
#ifdef SHOW_ASSERT
mDebugLastState = state;
// Make sure we only call ref() once and in balance with unref().
if (state == bs_initialize)
{
// This -- and call to ref() (and the test when we're about to call unref()) -- is all done in the critical area of mMultiplexMutex.
llassert(!mDebugRefCalled);
mDebugRefCalled = true;
}
#endif
switch(state)
{
case bs_reset:
// We're just being kick started to get into the right thread
// (possibly for the second time when a late abort was detected, but that's ok: we do nothing here).
break;
case bs_initialize:
ref();
initialize_impl();
break;
case bs_multiplex:
llassert(!mDebugAborted);
multiplex_impl(run_state);
break;
case bs_abort:
abort_impl();
break;
case bs_finish:
sub_state_type_wat(mSubState)->reset = false; // By default, halt state machines when finished.
finish_impl(); // Call run() from finish_impl() or the call back to restart from the beginning.
break;
case bs_callback:
callback();
break;
case bs_killed:
mRunMutex.unlock();
// bs_killed is handled when it is set. So, this must be a re-entry.
// We can only get here when being called by an engine that we were added to before we were killed.
// This should already be have been set to NULL to indicate that we want to be removed from that engine.
llassert(!multiplex_state_type_rat(mState)->current_engine);
// Do not call unref() twice.
return;
}
mRunMutex.unlock();
}
{
multiplex_state_type_wat state_w(mState);
//=================================
// Start of critical area of mState
// Unless the state is bs_multiplex or bs_killed, the state machine needs to keep calling multiplex().
bool need_new_run = true;
if (event == normal_run || event == insert_abort)
{
sub_state_type_rat sub_state_r(mSubState);
if (event == normal_run)
{
// Switch base state as function of sub state.
switch(state)
{
case bs_reset:
if (sub_state_r->aborted)
{
// We have been aborted before we could even initialize, no de-initialization is possible.
state_w->base_state = bs_killed;
// Stop running.
need_new_run = false;
}
else
{
// run() was called: call initialize_impl() next.
state_w->base_state = bs_initialize;
}
break;
case bs_initialize:
if (sub_state_r->aborted)
{
// initialize_impl() called abort.
state_w->base_state = bs_abort;
}
else
{
// Start actually running.
state_w->base_state = bs_multiplex;
// If the state is bs_multiplex we only need to run again when need_run was set again in the meantime or when this state machine isn't idle.
need_new_run = sub_state_r->need_run || !sub_state_r->idle;
}
break;
case bs_multiplex:
if (sub_state_r->aborted)
{
// abort() was called.
state_w->base_state = bs_abort;
}
else if (sub_state_r->finished)
{
// finish() was called.
state_w->base_state = bs_finish;
}
else
{
// Continue in bs_multiplex.
// If the state is bs_multiplex we only need to run again when need_run was set again in the meantime or when this state machine isn't idle.
need_new_run = sub_state_r->need_run || !sub_state_r->idle;
// If this fails then the run state didn't change and neither idle() nor yield() was called.
llassert_always(!(need_new_run && !sub_state_r->skip_idle && !mYieldEngine && sub_state_r->run_state == run_state));
}
break;
case bs_abort:
// After calling abort_impl(), call finish_impl().
state_w->base_state = bs_finish;
break;
case bs_finish:
// After finish_impl(), call the call back function.
state_w->base_state = bs_callback;
break;
case bs_callback:
if (sub_state_r->reset)
{
// run() was called (not followed by kill()).
state_w->base_state = bs_reset;
}
else
{
// After the call back, we're done.
state_w->base_state = bs_killed;
// Call unref().
destruct = true;
// Stop running.
need_new_run = false;
}
break;
default: // bs_killed
// We never get here.
break;
}
}
else // event == insert_abort
{
// We have been aborted, but we're idle. If we'd just schedule a new run below, it would re-run
// the last state before the abort is handled. What we really need is to pick up as if the abort
// was handled directly after returning from the last run. If we're not running anymore, then
// do nothing as the state machine already ran and things should be processed normally
// (in that case this is just a normal schedule which can't harm because we're can't accidently
// re-run an old run_state).
if (state_w->base_state == bs_multiplex) // Still running?
{
// See the switch above for case bs_multiplex.
llassert(sub_state_r->aborted);
// abort() was called.
state_w->base_state = bs_abort;
}
}
#ifdef CWDEBUG
if (state != state_w->base_state)
Dout(dc::statemachine(mSMDebug), "Base state changed from " << state_str(state) << " to " << state_str(state_w->base_state) <<
"; need_new_run = " << (need_new_run ? "true" : "false") << " [" << (void*)this << "]");
#endif
}
// Figure out in which engine we should run.
AIEngine* engine = mYieldEngine ? mYieldEngine : (state_w->current_engine ? state_w->current_engine : mDefaultEngine);
// And the current engine we're running in.
AIEngine* current_engine = (event == normal_run) ? state_w->current_engine : NULL;
// Immediately run again if yield() wasn't called and it's OK to run in this thread.
// Note that when it's OK to run in any engine (mDefaultEngine is NULL) then the last
// compare is also true when current_engine == NULL.
keep_looping = need_new_run && !mYieldEngine && engine == current_engine;
mYieldEngine = NULL;
if (keep_looping)
{
// Start a new loop.
run_state = begin_loop((state = state_w->base_state));
event = normal_run;
}
else
{
if (need_new_run)
{
// Add us to an engine if necessary.
if (engine != state_w->current_engine)
{
// engine can't be NULL here: it can only be NULL if mDefaultEngine is NULL.
engine->add(this);
// Mark that we're added to this engine, and at the same time, that we're not added to the previous one.
state_w->current_engine = engine;
}
}
else
{
// Remove this state machine from any engine,
// causing the engine to remove us.
state_w->current_engine = NULL;
}
#ifdef SHOW_ASSERT
// Mark that we stop running the loop.
mThreadId.clear();
if (destruct)
{
// We're about to call unref(). Make sure we call that in balance with ref()!
llassert(mDebugRefCalled);
mDebugRefCalled = false;
}
#endif
// End of critical area of mMultiplexMutex.
//=========================================
// Release the lock on mMultiplexMutex *first*, before releasing the lock on mState,
// to avoid to ever call the tryLock() and fail, while this thread isn't still
// BEFORE the critical area of mState!
mMultiplexMutex.unlock();
}
// Now it is safe to leave the critical area of mState as the tryLock won't fail anymore.
// (Or, if we didn't release mMultiplexMutex because keep_looping is true, then this
// end of the critical area of mState is equivalent to the first critical area in this
// function.
// End of critical area of mState.
//================================
}
}
while (keep_looping);
if (destruct)
{
unref();
}
}
AIStateMachine::state_type AIStateMachine::begin_loop(base_state_type base_state)
{
DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::begin_loop(" << state_str(base_state) << ") [" << (void*)this << "]");
sub_state_type_wat sub_state_w(mSubState);
// Honor a subsequent call to idle() (only necessary in bs_multiplex, but it doesn't hurt to reset this flag in other states too).
sub_state_w->skip_idle = false;
// Mark that we're about to honor all previous run requests.
sub_state_w->need_run = false;
// Honor previous calls to advance_state() (once run_state is initialized).
if (base_state == bs_multiplex && sub_state_w->advance_state > sub_state_w->run_state)
{
Dout(dc::statemachine(mSMDebug), "Copying advance_state to run_state, because it is larger [" << state_str_impl(sub_state_w->advance_state) << " > " << state_str_impl(sub_state_w->run_state) << "]");
sub_state_w->run_state = sub_state_w->advance_state;
}
#ifdef SHOW_ASSERT
else
{
// If advance_state wasn't honored then it isn't a reason to run.
// We're running anyway, but that should be because set_state() was called.
mDebugAdvanceStatePending = false;
}
#endif
sub_state_w->advance_state = 0;
#ifdef SHOW_ASSERT
// Mark that we're running the loop.
mThreadId.reset();
// This point marks handling cont().
mDebugShouldRun |= mDebugContPending;
mDebugContPending = false;
// This point also marks handling advance_state().
mDebugShouldRun |= mDebugAdvanceStatePending;
mDebugAdvanceStatePending = false;
#endif
// Make a copy of the state that we're about to run.
return sub_state_w->run_state;
}
void AIStateMachine::run(AIStateMachine* parent, state_type new_parent_state, bool abort_parent, bool on_abort_signal_parent, AIEngine* default_engine)
{
DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::run(" <<
(void*)parent << ", " <<
(parent ? parent->state_str_impl(new_parent_state) : "NA") <<
", abort_parent = " << (abort_parent ? "true" : "false") <<
", on_abort_signal_parent = " << (on_abort_signal_parent ? "true" : "false") <<
", default_engine = " << (default_engine ? default_engine->name() : "NULL") << ") [" << (void*)this << "]");
#ifdef SHOW_ASSERT
{
multiplex_state_type_rat state_r(mState);
// Can only be run when in one of these states.
llassert(state_r->base_state == bs_reset || state_r->base_state == bs_finish || state_r->base_state == bs_callback);
// Must be the first time we're being run, or we must be called from finish_impl or a callback function.
llassert(!(state_r->base_state == bs_reset && (mParent || mCallback)));
}
#endif
// Store the requested default engine.
mDefaultEngine = default_engine;
// Initialize sleep timer.
mSleep = 0;
// Allow NULL to be passed as parent to signal that we want to reuse the old one.
if (parent)
{
mParent = parent;
// In that case remove any old callback!
if (mCallback)
{
delete mCallback;
mCallback = NULL;
}
mNewParentState = new_parent_state;
mAbortParent = abort_parent;
mOnAbortSignalParent = on_abort_signal_parent;
}
// If abort_parent is requested then a parent must be provided.
llassert(!abort_parent || mParent);
// If a parent is provided, it must be running.
llassert(!mParent || mParent->running());
// Start from the beginning.
reset();
}
void AIStateMachine::run(callback_type::signal_type::slot_type const& slot, AIEngine* default_engine)
{
DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::run(<slot>, default_engine = " << default_engine->name() << ") [" << (void*)this << "]");
#ifdef SHOW_ASSERT
{
multiplex_state_type_rat state_r(mState);
// Can only be run when in one of these states.
llassert(state_r->base_state == bs_reset || state_r->base_state == bs_finish || state_r->base_state == bs_callback);
// Must be the first time we're being run, or we must be called from finish_impl or a callback function.
llassert(!(state_r->base_state == bs_reset && (mParent || mCallback)));
}
#endif
// Store the requested default engine.
mDefaultEngine = default_engine;
// Initialize sleep timer.
mSleep = 0;
// Clean up any old callbacks.
mParent = NULL;
if (mCallback)
{
delete mCallback;
mCallback = NULL;
}
// Create new call back.
mCallback = new callback_type(slot);
// Start from the beginning.
reset();
}
void AIStateMachine::callback(void)
{
DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::callback() [" << (void*)this << "]");
bool aborted = sub_state_type_rat(mSubState)->aborted;
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 (aborted && mAbortParent)
{
mParent->abort();
mParent = NULL;
}
else if (!aborted || mOnAbortSignalParent)
{
mParent->advance_state(mNewParentState);
}
}
}
if (mCallback)
{
mCallback->callback(!aborted);
if (multiplex_state_type_rat(mState)->base_state != bs_reset)
{
delete mCallback;
mCallback = NULL;
mParent = NULL;
}
}
else
{
// Not restarted by callback. Allow run() to be called later on.
mParent = NULL;
}
}
void AIStateMachine::force_killed(void)
{
multiplex_state_type_wat state_w(mState);
state_w->base_state = bs_killed;
}
void AIStateMachine::kill(void)
{
DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::kill() [" << (void*)this << "]");
#ifdef SHOW_ASSERT
{
multiplex_state_type_rat state_r(mState);
// kill() may only be called from the call back function.
llassert(state_r->base_state == bs_callback);
// May only be called by the thread that is holding mMultiplexMutex.
llassert(mThreadId.equals_current_thread());
}
#endif
sub_state_type_wat sub_state_w(mSubState);
// Void last call to run() (ie from finish_impl()), if any.
sub_state_w->reset = false;
}
void AIStateMachine::reset()
{
DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::reset() [" << (void*)this << "]");
#ifdef SHOW_ASSERT
mDebugAborted = false;
mDebugContPending = false;
mDebugSetStatePending = false;
mDebugRefCalled = false;
#endif
mRuntime = 0;
bool inside_multiplex;
{
multiplex_state_type_rat state_r(mState);
// reset() is only called from run(), which may only be called when just created, from finish_impl() or from the call back function.
llassert(state_r->base_state == bs_reset || state_r->base_state == bs_finish || state_r->base_state == bs_callback);
inside_multiplex = state_r->base_state != bs_reset;
}
{
sub_state_type_wat sub_state_w(mSubState);
// Reset.
sub_state_w->aborted = sub_state_w->finished = false;
// Signal that we want to start running from the beginning.
sub_state_w->reset = true;
// Start running.
sub_state_w->idle = false;
// We're not waiting for a condition.
sub_state_w->blocked = NULL;
// Keep running till we reach at least bs_multiplex.
sub_state_w->need_run = true;
}
if (!inside_multiplex)
{
// Kick start the state machine.
multiplex(initial_run);
}
}
void AIStateMachine::set_state(state_type new_state)
{
DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::set_state(" << state_str_impl(new_state) << ") [" << (void*)this << "]");
#ifdef SHOW_ASSERT
{
multiplex_state_type_rat state_r(mState);
// set_state() may only be called from initialize_impl() or multiplex_impl().
llassert(state_r->base_state == bs_initialize || state_r->base_state == bs_multiplex);
// May only be called by the thread that is holding mMultiplexMutex. If this fails, you probably called set_state() by accident instead of advance_state().
llassert(mThreadId.equals_current_thread());
}
#endif
sub_state_type_wat sub_state_w(mSubState);
// It should never happen that set_state() is called while we're blocked.
llassert(!sub_state_w->blocked);
// Force current state to the requested state.
sub_state_w->run_state = new_state;
// Void last call to advance_state.
sub_state_w->advance_state = 0;
// Void last call to idle(), if any.
sub_state_w->idle = false;
// Honor a subsequent call to idle().
sub_state_w->skip_idle = false;
#ifdef SHOW_ASSERT
// We should run. This can only be cancelled by a call to idle().
mDebugSetStatePending = true;
#endif
}
void AIStateMachine::advance_state(state_type new_state)
{
DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::advance_state(" << state_str_impl(new_state) << ") [" << (void*)this << "]");
{
sub_state_type_wat sub_state_w(mSubState);
// Ignore call to advance_state when the currently queued state is already greater or equal to the requested state.
if (sub_state_w->advance_state >= new_state)
{
Dout(dc::statemachine(mSMDebug), "Ignored, because " << state_str_impl(sub_state_w->advance_state) << " >= " << state_str_impl(new_state) << ".");
return;
}
// Ignore call to advance_state when the current state is greater than the requested state: the new state would be
// ignored in begin_loop(), as is already remarked there: an advanced state that is not honored is not a reason to run.
// This call might as well not have happened. Not returning here is a bug because that is effectively a cont(), while
// the state change is and should be being ignored: the statemachine would start running it's current state (again).
if (sub_state_w->run_state > new_state)
{
Dout(dc::statemachine(mSMDebug), "Ignored, because " << state_str_impl(sub_state_w->run_state) << " > " << state_str_impl(new_state) << " (current state).");
return;
}
// Increment state.
sub_state_w->advance_state = new_state;
// Void last call to idle(), if any.
sub_state_w->idle = false;
// Ignore a call to idle if it occurs before we leave multiplex_impl().
sub_state_w->skip_idle = true;
// No longer say we woke up when signalled() is called.
if (sub_state_w->blocked)
{
Dout(dc::statemachine(mSMDebug), "Removing statemachine from condition " << (void*)sub_state_w->blocked);
sub_state_w->blocked->remove(this);
sub_state_w->blocked = NULL;
}
// Mark that a re-entry of multiplex() is necessary.
sub_state_w->need_run = true;
#ifdef SHOW_ASSERT
// From this moment on.
mDebugAdvanceStatePending = true;
// If the new state is equal to the current state, then this should be considered to be a cont()
// because also equal states are ignored in begin_loop(). However, unlike a cont() we ignore a call
// to idle() when the statemachine is already running in this state (because that is a race condition
// and ignoring the idle() is the most logical thing to do then). Hence we treated this as a full
// fletched advance_state but need to tell the debug code that it's really also a cont().
if (sub_state_w->run_state == new_state)
{
// From this moment.
mDebugContPending = true;
}
#endif
}
if (!mMultiplexMutex.isSelfLocked())
{
multiplex(schedule_run);
}
}
void AIStateMachine::idle(void)
{
DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::idle() [" << (void*)this << "]");
#ifdef SHOW_ASSERT
{
multiplex_state_type_rat state_r(mState);
// idle() may only be called from initialize_impl() or multiplex_impl().
llassert(state_r->base_state == bs_multiplex || state_r->base_state == bs_initialize);
// May only be called by the thread that is holding mMultiplexMutex.
llassert(mThreadId.equals_current_thread());
}
// idle() following set_state() cancels the reason to run because of the call to set_state.
mDebugSetStatePending = false;
#endif
sub_state_type_wat sub_state_w(mSubState);
// As idle may only be called from within the state machine, it should never happen that the state machine is already idle.
llassert(!sub_state_w->idle);
// Ignore call to idle() when advance_state() was called since last call to set_state().
if (sub_state_w->skip_idle)
{
Dout(dc::statemachine(mSMDebug), "Ignored, because skip_idle is true (advance_state() was called last).");
return;
}
// Mark that we are idle.
sub_state_w->idle = true;
// Not sleeping (anymore).
mSleep = 0;
}
// This function is very much like idle().
void AIStateMachine::wait(AIConditionBase& condition)
{
DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::wait(" << (void*)&condition << ") [" << (void*)this << "]");
#ifdef SHOW_ASSERT
{
multiplex_state_type_rat state_r(mState);
// wait() may only be called multiplex_impl().
llassert(state_r->base_state == bs_multiplex);
// May only be called by the thread that is holding mMultiplexMutex.
llassert(mThreadId.equals_current_thread());
}
// wait() following set_state() cancels the reason to run because of the call to set_state.
mDebugSetStatePending = false;
#endif
sub_state_type_wat sub_state_w(mSubState);
// As wait() may only be called from within the state machine, it should never happen that the state machine is already idle.
llassert(!sub_state_w->idle);
// Ignore call to wait() when advance_state() was called since last call to set_state().
if (sub_state_w->skip_idle)
{
Dout(dc::statemachine(mSMDebug), "Ignored, because skip_idle is true (advance_state() was called last).");
return;
}
// Register ourselves with the condition object.
condition.wait(this);
// Mark that we are idle.
sub_state_w->idle = true;
// Mark that we are waiting for a condition.
sub_state_w->blocked = &condition;
// Not sleeping (anymore).
mSleep = 0;
}
void AIStateMachine::cont(void)
{
DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::cont() [" << (void*)this << "]");
{
sub_state_type_wat sub_state_w(mSubState);
// Void last call to idle(), if any.
sub_state_w->idle = false;
// No longer say we woke up when signalled() is called.
if (sub_state_w->blocked)
{
Dout(dc::statemachine(mSMDebug), "Removing statemachine from condition " << (void*)sub_state_w->blocked);
sub_state_w->blocked->remove(this);
sub_state_w->blocked = NULL;
}
// Mark that a re-entry of multiplex() is necessary.
sub_state_w->need_run = true;
#ifdef SHOW_ASSERT
// From this moment.
mDebugContPending = true;
#endif
}
if (!mMultiplexMutex.isSelfLocked())
{
multiplex(schedule_run);
}
}
// This function is very much like cont(), except that it has no effect when we are not in a blocked state.
// Returns true if the state machine was unblocked, false if it was already unblocked.
bool AIStateMachine::signalled(void)
{
DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::signalled() [" << (void*)this << "]");
{
sub_state_type_wat sub_state_w(mSubState);
// Test if we are blocked or not.
if (sub_state_w->blocked)
{
Dout(dc::statemachine(mSMDebug), "Removing statemachine from condition " << (void*)sub_state_w->blocked);
sub_state_w->blocked->remove(this);
sub_state_w->blocked = NULL;
}
else
{
return false;
}
// Void last call to wait().
sub_state_w->idle = false;
// Mark that a re-entry of multiplex() is necessary.
sub_state_w->need_run = true;
#ifdef SHOW_ASSERT
// From this moment.
mDebugContPending = true;
#endif
}
if (!mMultiplexMutex.isSelfLocked())
{
multiplex(schedule_run);
}
return true;
}
void AIStateMachine::abort(void)
{
DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::abort() [" << (void*)this << "]");
bool is_waiting = false;
{
multiplex_state_type_rat state_r(mState);
sub_state_type_wat sub_state_w(mSubState);
// Mark that we are aborted, iff we didn't already finish.
sub_state_w->aborted = !sub_state_w->finished;
// No longer say we woke up when signalled() is called.
if (sub_state_w->blocked)
{
Dout(dc::statemachine(mSMDebug), "Removing statemachine from condition " << (void*)sub_state_w->blocked);
sub_state_w->blocked->remove(this);
sub_state_w->blocked = NULL;
}
// Mark that a re-entry of multiplex() is necessary.
sub_state_w->need_run = true;
// Schedule a new run when this state machine is waiting.
is_waiting = state_r->base_state == bs_multiplex && sub_state_w->idle;
}
if (is_waiting && !mMultiplexMutex.isSelfLocked())
{
multiplex(insert_abort);
}
// Block until the current run finished.
if (!mRunMutex.tryLock())
{
llwarns << "AIStateMachine::abort() blocks because the statemachine is still executing code in another thread." << llendl;
mRunMutex.lock();
}
mRunMutex.unlock();
#ifdef SHOW_ASSERT
// When abort() returns, it may never run again.
mDebugAborted = true;
#endif
}
void AIStateMachine::finish(void)
{
DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::finish() [" << (void*)this << "]");
#ifdef SHOW_ASSERT
{
multiplex_state_type_rat state_r(mState);
// finish() may only be called from multiplex_impl().
llassert(state_r->base_state == bs_multiplex);
// May only be called by the thread that is holding mMultiplexMutex.
llassert(mThreadId.equals_current_thread());
}
#endif
sub_state_type_wat sub_state_w(mSubState);
// finish() should not be called when idle.
llassert(!sub_state_w->idle);
// Mark that we are finished.
sub_state_w->finished = true;
}
void AIStateMachine::yield(void)
{
DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::yield() [" << (void*)this << "]");
multiplex_state_type_rat state_r(mState);
// yield() may only be called from multiplex_impl().
llassert(state_r->base_state == bs_multiplex);
// May only be called by the thread that is holding mMultiplexMutex.
llassert(mThreadId.equals_current_thread());
// Set mYieldEngine to the best non-NUL value.
mYieldEngine = state_r->current_engine ? state_r->current_engine : (mDefaultEngine ? mDefaultEngine : &gStateMachineThreadEngine);
}
void AIStateMachine::yield(AIEngine* engine)
{
llassert(engine);
DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::yield(" << engine->name() << ") [" << (void*)this << "]");
#ifdef SHOW_ASSERT
{
multiplex_state_type_rat state_r(mState);
// yield() may only be called from multiplex_impl().
llassert(state_r->base_state == bs_multiplex);
// May only be called by the thread that is holding mMultiplexMutex.
llassert(mThreadId.equals_current_thread());
}
#endif
mYieldEngine = engine;
}
bool AIStateMachine::yield_if_not(AIEngine* engine)
{
if (engine && multiplex_state_type_rat(mState)->current_engine != engine)
{
yield(engine);
return true;
}
return false;
}
void AIStateMachine::yield_frame(unsigned int frames)
{
DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::yield_frame(" << frames << ") [" << (void*)this << "]");
mSleep = -(S64)frames;
// Sleeping is always done from the main thread.
yield(&gMainThreadEngine);
}
void AIStateMachine::yield_ms(unsigned int ms)
{
DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::yield_ms(" << ms << ") [" << (void*)this << "]");
mSleep = get_clock_count() + calc_clock_frequency() * ms / 1000;
// Sleeping is always done from the main thread.
yield(&gMainThreadEngine);
}
char const* AIStateMachine::state_str(base_state_type state)
{
switch(state)
{
AI_CASE_RETURN(bs_reset);
AI_CASE_RETURN(bs_initialize);
AI_CASE_RETURN(bs_multiplex);
AI_CASE_RETURN(bs_abort);
AI_CASE_RETURN(bs_finish);
AI_CASE_RETURN(bs_callback);
AI_CASE_RETURN(bs_killed);
}
llassert(false);
return "UNKNOWN BASE STATE";
}
AIEngine gMainThreadEngine("gMainThreadEngine");
AIEngine gStateMachineThreadEngine("gStateMachineThreadEngine");
// State Machine Thread main loop.
void AIEngine::threadloop(void)
{
queued_type::iterator queued_element, end;
{
engine_state_type_wat engine_state_w(mEngineState);
end = engine_state_w->list.end();
queued_element = engine_state_w->list.begin();
if (queued_element == end)
{
// Nothing to do. Wait till something is added to the queue again.
engine_state_w->waiting = true;
engine_state_w.wait();
engine_state_w->waiting = false;
return;
}
}
do
{
AIStateMachine& state_machine(queued_element->statemachine());
state_machine.multiplex(AIStateMachine::normal_run);
bool active = state_machine.active(this); // This locks mState shortly, so it must be called before locking mEngineState because add() locks mEngineState while holding mState.
engine_state_type_wat engine_state_w(mEngineState);
if (!active)
{
Dout(dc::statemachine(state_machine.mSMDebug), "Erasing state machine [" << (void*)&state_machine << "] from " << mName);
engine_state_w->list.erase(queued_element++);
}
else
{
++queued_element;
}
}
while (queued_element != end);
}
void AIEngine::wake_up(void)
{
engine_state_type_wat engine_state_w(mEngineState);
if (engine_state_w->waiting)
{
engine_state_w.signal();
}
}
//-----------------------------------------------------------------------------
// AIEngineThread
class AIEngineThread : public LLThread
{
public:
static AIEngineThread* sInstance;
bool volatile mRunning;
public:
// MAIN-THREAD
AIEngineThread(void);
virtual ~AIEngineThread();
protected:
virtual void run(void);
};
//static
AIEngineThread* AIEngineThread::sInstance;
AIEngineThread::AIEngineThread(void) : LLThread("AIEngineThread"), mRunning(true)
{
}
AIEngineThread::~AIEngineThread(void)
{
}
void AIEngineThread::run(void)
{
while(mRunning)
{
gStateMachineThreadEngine.threadloop();
}
}
void startEngineThread(void)
{
AIEngineThread::sInstance = new AIEngineThread;
AIEngineThread::sInstance->start();
}
void stopEngineThread(void)
{
AIEngineThread::sInstance->mRunning = false;
gStateMachineThreadEngine.wake_up();
int count = 401;
while(--count && !AIEngineThread::sInstance->isStopped())
{
ms_sleep(10);
}
llinfos << "State machine thread" << (!AIEngineThread::sInstance->isStopped() ? " not" : "") << " stopped after " << ((400 - count) * 10) << "ms." << llendl;
}