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.
This commit is contained in:
@@ -21,6 +21,7 @@ set(aistatemachine_SOURCE_FILES
|
||||
aistatemachine.cpp
|
||||
aistatemachinethread.cpp
|
||||
aitimer.cpp
|
||||
aicondition.cpp
|
||||
)
|
||||
|
||||
set(aistatemachine_HEADER_FILES
|
||||
@@ -29,6 +30,7 @@ set(aistatemachine_HEADER_FILES
|
||||
aistatemachine.h
|
||||
aistatemachinethread.h
|
||||
aitimer.h
|
||||
aicondition.h
|
||||
)
|
||||
|
||||
set_source_files_properties(${aistatemachine_HEADER_FILES}
|
||||
|
||||
88
indra/aistatemachine/aicondition.cpp
Normal file
88
indra/aistatemachine/aicondition.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* @file aicondition.cpp
|
||||
* @brief Implementation of AICondition
|
||||
*
|
||||
* Copyright (c) 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.
|
||||
*
|
||||
* 14/10/2013
|
||||
* Initial version, written by Aleric Inglewood @ SL
|
||||
*/
|
||||
|
||||
#include "aicondition.h"
|
||||
#include "aistatemachine.h"
|
||||
|
||||
void AIConditionBase::wait(AIStateMachine* state_machine)
|
||||
{
|
||||
// The condition must be locked before calling AIStateMachine::wait().
|
||||
llassert(mutex().isSelfLocked());
|
||||
// Add the new state machine at the end.
|
||||
mWaitingStateMachines.push_back(state_machine);
|
||||
}
|
||||
|
||||
void AIConditionBase::remove(AIStateMachine* state_machine)
|
||||
{
|
||||
mutex().lock();
|
||||
// Remove all occurances of state_machine from the queue.
|
||||
queue_t::iterator const end = mWaitingStateMachines.end();
|
||||
queue_t::iterator last = end;
|
||||
for (queue_t::iterator iter = mWaitingStateMachines.begin(); iter != last; ++iter)
|
||||
{
|
||||
if (iter->get() == state_machine)
|
||||
{
|
||||
if (--last == iter)
|
||||
{
|
||||
break;
|
||||
}
|
||||
queue_t::value_type::swap(*iter, *last);
|
||||
}
|
||||
}
|
||||
// This invalidates all iterators involved, including end, but not any iterators to the remaining elements.
|
||||
mWaitingStateMachines.erase(last, end);
|
||||
mutex().unlock();
|
||||
}
|
||||
|
||||
void AIConditionBase::signal(int n)
|
||||
{
|
||||
// The condition must be locked before calling AICondition::signal or AICondition::broadcast.
|
||||
llassert(mutex().isSelfLocked());
|
||||
// Signal n state machines.
|
||||
while (n > 0 && !mWaitingStateMachines.empty())
|
||||
{
|
||||
LLPointer<AIStateMachine> state_machine = mWaitingStateMachines.front();
|
||||
bool success = state_machine->signalled();
|
||||
// Only state machines that are actually still blocked should be in the queue:
|
||||
// they are removed from the queue by calling AICondition::remove whenever
|
||||
// they are unblocked for whatever reason...
|
||||
llassert(success);
|
||||
if (success)
|
||||
{
|
||||
++n;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We never get here...
|
||||
remove(state_machine.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
110
indra/aistatemachine/aicondition.h
Normal file
110
indra/aistatemachine/aicondition.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* @file aicondition.h
|
||||
* @brief Condition variable for statemachines.
|
||||
*
|
||||
* Copyright (c) 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.
|
||||
*
|
||||
* 14/10/2013
|
||||
* Initial version, written by Aleric Inglewood @ SL
|
||||
*/
|
||||
|
||||
#ifndef AICONDITION_H
|
||||
#define AICONDITION_H
|
||||
|
||||
#include <deque>
|
||||
#include <llpointer.h>
|
||||
#include "aithreadsafe.h"
|
||||
|
||||
class AIStateMachine;
|
||||
class LLMutex;
|
||||
|
||||
// class AICondition
|
||||
//
|
||||
// Call AIStateMachine::wait(AICondition&) in the multiplex_impl of a state machine to
|
||||
// make the state machine go idle until some thread calls AICondition::signal().
|
||||
//
|
||||
// If the state machine is no longer running or wasn't waiting anymore because
|
||||
// something else woke it up, then AICondition::signal() will wake up another
|
||||
// state machine (if any).
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// struct Foo { bool met(); }; // Returns true when the condition is met.
|
||||
// AICondition<Foo> Condition_t;
|
||||
// AIAccess<Foo> Condition_wat;
|
||||
//
|
||||
// // Some thread-safe condition variable.
|
||||
// Condition_t condition;
|
||||
//
|
||||
// // Inside the state machine:
|
||||
// {
|
||||
// ...
|
||||
// state WAIT_FOR_CONDITION:
|
||||
// {
|
||||
// // Lock condition and check it. Wait if condition is not met yet.
|
||||
// {
|
||||
// Condition_wat condition_w(condition);
|
||||
// if (!condition_w->met())
|
||||
// {
|
||||
// wait(condition);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// set_state(CONDITION_MET);
|
||||
// break;
|
||||
// }
|
||||
// CONDITION_MET:
|
||||
// {
|
||||
//
|
||||
|
||||
class AIConditionBase
|
||||
{
|
||||
public:
|
||||
virtual ~AIConditionBase() { }
|
||||
|
||||
void signal(int n = 1); // Call this when the condition was met to release n state machines.
|
||||
void broadcast(void) { signal(mWaitingStateMachines.size()); } // Release all blocked state machines.
|
||||
|
||||
private:
|
||||
// These functions are called by AIStateMachine.
|
||||
friend class AIStateMachine;
|
||||
void wait(AIStateMachine* state_machine);
|
||||
void remove(AIStateMachine* state_machine);
|
||||
|
||||
protected:
|
||||
virtual LLMutex& mutex(void) = 0;
|
||||
|
||||
protected:
|
||||
typedef std::deque<LLPointer<AIStateMachine> > queue_t;
|
||||
queue_t mWaitingStateMachines;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class AICondition : public AIThreadSafeSimpleDC<T>, public AIConditionBase
|
||||
{
|
||||
protected:
|
||||
/*virtual*/ LLMutex& mutex(void) { return this->mMutex; }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
|
||||
#include "linden_common.h"
|
||||
#include "aistatemachine.h"
|
||||
#include "aicondition.h"
|
||||
#include "lltimer.h"
|
||||
|
||||
//==================================================================
|
||||
@@ -960,6 +961,8 @@ void AIStateMachine::reset()
|
||||
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;
|
||||
}
|
||||
@@ -983,6 +986,8 @@ void AIStateMachine::set_state(state_type new_state)
|
||||
}
|
||||
#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.
|
||||
@@ -1023,6 +1028,13 @@ void AIStateMachine::advance_state(state_type new_state)
|
||||
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
|
||||
@@ -1075,6 +1087,40 @@ void AIStateMachine::idle(void)
|
||||
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 << "]");
|
||||
@@ -1082,6 +1128,13 @@ void AIStateMachine::cont(void)
|
||||
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
|
||||
@@ -1095,6 +1148,40 @@ void AIStateMachine::cont(void)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 << "]");
|
||||
@@ -1104,6 +1191,13 @@ void AIStateMachine::abort(void)
|
||||
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.
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include <list>
|
||||
#include <boost/signals2.hpp>
|
||||
|
||||
class AIConditionBase;
|
||||
class AIStateMachine;
|
||||
|
||||
class AIEngine
|
||||
@@ -132,6 +133,7 @@ class AIStateMachine : public LLThreadSafeRefCount
|
||||
struct sub_state_type {
|
||||
state_type run_state;
|
||||
state_type advance_state;
|
||||
AIConditionBase* blocked;
|
||||
bool reset;
|
||||
bool need_run;
|
||||
bool idle;
|
||||
@@ -232,6 +234,7 @@ class AIStateMachine : public LLThreadSafeRefCount
|
||||
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'.
|
||||
@@ -245,11 +248,12 @@ class AIStateMachine : public LLThreadSafeRefCount
|
||||
// to access this state machine.
|
||||
void abort(void); // Abort the state machine (unsuccessful finish).
|
||||
|
||||
// These are the only two functions that can be called by any thread at any moment.
|
||||
// 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.
|
||||
|
||||
Reference in New Issue
Block a user