diff --git a/indra/aistatemachine/CMakeLists.txt b/indra/aistatemachine/CMakeLists.txt index 6db29a007..806261201 100644 --- a/indra/aistatemachine/CMakeLists.txt +++ b/indra/aistatemachine/CMakeLists.txt @@ -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} diff --git a/indra/aistatemachine/aicondition.cpp b/indra/aistatemachine/aicondition.cpp new file mode 100644 index 000000000..fa35104a8 --- /dev/null +++ b/indra/aistatemachine/aicondition.cpp @@ -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 . + * + * 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 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()); + } + } +} + diff --git a/indra/aistatemachine/aicondition.h b/indra/aistatemachine/aicondition.h new file mode 100644 index 000000000..05dd9ea42 --- /dev/null +++ b/indra/aistatemachine/aicondition.h @@ -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 . + * + * 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 +#include +#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 Condition_t; +// AIAccess 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 > queue_t; + queue_t mWaitingStateMachines; +}; + +template +class AICondition : public AIThreadSafeSimpleDC, public AIConditionBase +{ + protected: + /*virtual*/ LLMutex& mutex(void) { return this->mMutex; } +}; + +#endif + diff --git a/indra/aistatemachine/aistatemachine.cpp b/indra/aistatemachine/aistatemachine.cpp index bf867fffd..fcdb2d9c8 100644 --- a/indra/aistatemachine/aistatemachine.cpp +++ b/indra/aistatemachine/aistatemachine.cpp @@ -33,6 +33,7 @@ #include "linden_common.h" #include "aistatemachine.h" +#include "aicondition.h" #include "lltimer.h" //================================================================== @@ -283,7 +284,7 @@ char const* HelloWorld::state_str_impl(state_type run_state) const void AIEngine::add(AIStateMachine* state_machine) { - Dout(dc::statemachine, "Adding state machine [" << (void*)state_machine << "] to " << mName); + 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) @@ -330,7 +331,7 @@ void AIEngine::mainloop(void) engine_state_type_wat engine_state_w(mEngineState); if (!active) { - Dout(dc::statemachine, "Erasing state machine [" << (void*)&state_machine << "] from " << mName); + Dout(dc::statemachine(state_machine.mSMDebug), "Erasing state machine [" << (void*)&state_machine << "] from " << mName); engine_state_w->list.erase(queued_element++); } else @@ -392,7 +393,7 @@ 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, "AIStateMachine::multiplex(" << event_str(event) << ") [" << (void*)this << "]"); + DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::multiplex(" << event_str(event) << ") [" << (void*)this << "]"); base_state_type state; state_type run_state; @@ -407,7 +408,7 @@ void AIStateMachine::multiplex(event_type event) llassert(!mMultiplexMutex.isSelfLocked()); // We may never enter recursively! if (!mMultiplexMutex.tryLock()) { - Dout(dc::statemachine, "Leaving because it is already being run [" << (void*)this << "]"); + Dout(dc::statemachine(mSMDebug), "Leaving because it is already being run [" << (void*)this << "]"); return; } @@ -421,7 +422,7 @@ void AIStateMachine::multiplex(event_type event) // we should indeed run, again. if (event == schedule_run && !sub_state_type_rat(mSubState)->need_run) { - Dout(dc::statemachine, "Leaving because it was already being run [" << (void*)this << "]"); + Dout(dc::statemachine(mSMDebug), "Leaving because it was already being run [" << (void*)this << "]"); return; } @@ -440,9 +441,9 @@ void AIStateMachine::multiplex(event_type event) { #ifdef CWDEBUG if (state == bs_multiplex) - Dout(dc::statemachine, "Running state bs_multiplex / " << state_str_impl(run_state) << " [" << (void*)this << "]"); + Dout(dc::statemachine(mSMDebug), "Running state bs_multiplex / " << state_str_impl(run_state) << " [" << (void*)this << "]"); else - Dout(dc::statemachine, "Running state " << state_str(state) << " [" << (void*)this << "]"); + Dout(dc::statemachine(mSMDebug), "Running state " << state_str(state) << " [" << (void*)this << "]"); #endif #ifdef SHOW_ASSERT @@ -503,7 +504,7 @@ void AIStateMachine::multiplex(event_type event) // 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, "Late abort detected! Running state " << state_str(state) << " instead [" << (void*)this << "]"); + Dout(dc::statemachine(mSMDebug), "Late abort detected! Running state " << state_str(state) << " instead [" << (void*)this << "]"); #endif } #ifdef SHOW_ASSERT @@ -665,7 +666,7 @@ void AIStateMachine::multiplex(event_type event) #ifdef CWDEBUG if (state != state_w->base_state) - Dout(dc::statemachine, "Base state changed from " << state_str(state) << " to " << state_str(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 } @@ -699,11 +700,15 @@ void AIStateMachine::multiplex(event_type event) // 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; } +#ifdef SHOW_ASSERT + // We are leaving the loop, but we're not idle. The statemachine should re-enter the loop again. + mDebugShouldRun = true; +#endif } else { - // Remove this state machine from any engine. - // Cause the engine to remove us. + // Remove this state machine from any engine, + // causing the engine to remove us. state_w->current_engine = NULL; } @@ -749,7 +754,7 @@ void AIStateMachine::multiplex(event_type event) AIStateMachine::state_type AIStateMachine::begin_loop(base_state_type base_state) { - DoutEntering(dc::statemachine, "AIStateMachine::begin_loop(" << state_str(base_state) << ") [" << (void*)this << "]"); + 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). @@ -759,7 +764,7 @@ AIStateMachine::state_type AIStateMachine::begin_loop(base_state_type base_state // 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, "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) << "]"); + 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 @@ -789,7 +794,7 @@ AIStateMachine::state_type AIStateMachine::begin_loop(base_state_type base_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, "AIStateMachine::run(" << + DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::run(" << (void*)parent << ", " << (parent ? parent->state_str_impl(new_parent_state) : "NA") << ", abort_parent = " << (abort_parent ? "true" : "false") << @@ -839,7 +844,7 @@ void AIStateMachine::run(AIStateMachine* parent, state_type new_parent_state, bo void AIStateMachine::run(callback_type::signal_type::slot_type const& slot, AIEngine* default_engine) { - DoutEntering(dc::statemachine, "AIStateMachine::run(, default_engine = " << default_engine->name() << ") [" << (void*)this << "]"); + DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::run(, default_engine = " << default_engine->name() << ") [" << (void*)this << "]"); #ifdef SHOW_ASSERT { @@ -874,7 +879,7 @@ void AIStateMachine::run(callback_type::signal_type::slot_type const& slot, AIEn void AIStateMachine::callback(void) { - DoutEntering(dc::statemachine, "AIStateMachine::callback() [" << (void*)this << "]"); + DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::callback() [" << (void*)this << "]"); bool aborted = sub_state_type_rat(mSubState)->aborted; if (mParent) @@ -920,7 +925,7 @@ void AIStateMachine::force_killed(void) void AIStateMachine::kill(void) { - DoutEntering(dc::statemachine, "AIStateMachine::kill() [" << (void*)this << "]"); + DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::kill() [" << (void*)this << "]"); #ifdef SHOW_ASSERT { multiplex_state_type_rat state_r(mState); @@ -937,7 +942,7 @@ void AIStateMachine::kill(void) void AIStateMachine::reset() { - DoutEntering(dc::statemachine, "AIStateMachine::reset() [" << (void*)this << "]"); + DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::reset() [" << (void*)this << "]"); #ifdef SHOW_ASSERT mDebugAborted = false; mDebugContPending = false; @@ -960,6 +965,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; } @@ -972,7 +979,7 @@ void AIStateMachine::reset() void AIStateMachine::set_state(state_type new_state) { - DoutEntering(dc::statemachine, "AIStateMachine::set_state(" << state_str_impl(new_state) << ") [" << (void*)this << "]"); + DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::set_state(" << state_str_impl(new_state) << ") [" << (void*)this << "]"); #ifdef SHOW_ASSERT { multiplex_state_type_rat state_r(mState); @@ -983,6 +990,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. @@ -999,13 +1008,13 @@ void AIStateMachine::set_state(state_type new_state) void AIStateMachine::advance_state(state_type new_state) { - DoutEntering(dc::statemachine, "AIStateMachine::advance_state(" << state_str_impl(new_state) << ") [" << (void*)this << "]"); + 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, "Ignored, because " << state_str_impl(sub_state_w->advance_state) << " >= " << state_str_impl(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 @@ -1014,7 +1023,7 @@ void AIStateMachine::advance_state(state_type new_state) // 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, "Ignored, because " << state_str_impl(sub_state_w->run_state) << " > " << state_str_impl(new_state) << " (current 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. @@ -1023,6 +1032,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 @@ -1048,7 +1064,7 @@ void AIStateMachine::advance_state(state_type new_state) void AIStateMachine::idle(void) { - DoutEntering(dc::statemachine, "AIStateMachine::idle() [" << (void*)this << "]"); + DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::idle() [" << (void*)this << "]"); #ifdef SHOW_ASSERT { multiplex_state_type_rat state_r(mState); @@ -1066,7 +1082,7 @@ void AIStateMachine::idle(void) // Ignore call to idle() when advance_state() was called since last call to set_state(). if (sub_state_w->skip_idle) { - Dout(dc::statemachine, "Ignored, because skip_idle is true (advance_state() was called last)."); + Dout(dc::statemachine(mSMDebug), "Ignored, because skip_idle is true (advance_state() was called last)."); return; } // Mark that we are idle. @@ -1075,13 +1091,54 @@ 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, "AIStateMachine::cont() [" << (void*)this << "]"); + 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 @@ -1095,15 +1152,56 @@ 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, "AIStateMachine::abort() [" << (void*)this << "]"); + 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. @@ -1128,7 +1226,7 @@ void AIStateMachine::abort(void) void AIStateMachine::finish(void) { - DoutEntering(dc::statemachine, "AIStateMachine::finish() [" << (void*)this << "]"); + DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::finish() [" << (void*)this << "]"); #ifdef SHOW_ASSERT { multiplex_state_type_rat state_r(mState); @@ -1147,7 +1245,7 @@ void AIStateMachine::finish(void) void AIStateMachine::yield(void) { - DoutEntering(dc::statemachine, "AIStateMachine::yield() [" << (void*)this << "]"); + 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); @@ -1160,7 +1258,7 @@ void AIStateMachine::yield(void) void AIStateMachine::yield(AIEngine* engine) { llassert(engine); - DoutEntering(dc::statemachine, "AIStateMachine::yield(" << engine->name() << ") [" << (void*)this << "]"); + DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::yield(" << engine->name() << ") [" << (void*)this << "]"); #ifdef SHOW_ASSERT { multiplex_state_type_rat state_r(mState); @@ -1173,9 +1271,19 @@ void AIStateMachine::yield(AIEngine* engine) 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, "AIStateMachine::yield_frame(" << frames << ") [" << (void*)this << "]"); + DoutEntering(dc::statemachine(mSMDebug), "AIStateMachine::yield_frame(" << frames << ") [" << (void*)this << "]"); mSleep = -(S64)frames; // Sleeping is always done from the main thread. yield(&gMainThreadEngine); @@ -1183,7 +1291,7 @@ void AIStateMachine::yield_frame(unsigned int frames) void AIStateMachine::yield_ms(unsigned int ms) { - DoutEntering(dc::statemachine, "AIStateMachine::yield_ms(" << ms << ") [" << (void*)this << "]"); + 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); @@ -1233,7 +1341,7 @@ void AIEngine::threadloop(void) engine_state_type_wat engine_state_w(mEngineState); if (!active) { - Dout(dc::statemachine, "Erasing state machine [" << (void*)&state_machine << "] from " << mName); + Dout(dc::statemachine(state_machine.mSMDebug), "Erasing state machine [" << (void*)&state_machine << "] from " << mName); engine_state_w->list.erase(queued_element++); } else diff --git a/indra/aistatemachine/aistatemachine.h b/indra/aistatemachine/aistatemachine.h index 2b019c91f..047fe0515 100644 --- a/indra/aistatemachine/aistatemachine.h +++ b/indra/aistatemachine/aistatemachine.h @@ -39,6 +39,7 @@ #include #include +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; @@ -195,20 +197,36 @@ class AIStateMachine : public LLThreadSafeRefCount 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(void) : mCallback(NULL), mDefaultEngine(NULL), mYieldEngine(NULL), + 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. - virtual ~AIStateMachine() { llassert(multiplex_state_type_rat(mState)->base_state == bs_killed); } + // 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. @@ -224,11 +242,13 @@ 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::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 @@ -236,11 +256,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 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. diff --git a/indra/aistatemachine/aistatemachinethread.h b/indra/aistatemachine/aistatemachinethread.h index 780060bb6..2891cbb77 100644 --- a/indra/aistatemachine/aistatemachinethread.h +++ b/indra/aistatemachine/aistatemachinethread.h @@ -181,7 +181,11 @@ class AIStateMachineThreadBase : public AIStateMachine { static state_type const max_state = wait_stopped + 1; protected: - AIStateMachineThreadBase(void) { } + AIStateMachineThreadBase(CWD_ONLY(bool debug)) +#ifdef CWDEBUG + : AIStateMachine(debug) +#endif + { } private: // Handle initializing the object. @@ -217,7 +221,10 @@ class AIStateMachineThread : public AIStateMachineThreadBase { public: // Constructor. - AIStateMachineThread(void) + AIStateMachineThread(CWD_ONLY(bool debug)) +#ifdef CWDEBUG + : AIStateMachineThreadBase(debug) +#endif { *AIThreadImpl::StateMachineThread_wat(mThreadImpl.mStateMachineThread) = this; } diff --git a/indra/aistatemachine/aitimer.h b/indra/aistatemachine/aitimer.h index 5b028c6e0..3ee510007 100644 --- a/indra/aistatemachine/aitimer.h +++ b/indra/aistatemachine/aitimer.h @@ -76,7 +76,11 @@ class AITimer : public AIStateMachine { F64 mInterval; //!< Input variable: interval after which the event will be generated, in seconds. public: - AITimer(void) : mInterval(0) { DoutEntering(dc::statemachine, "AITimer(void) [" << (void*)this << "]"); } + AITimer(CWD_ONLY(bool debug = false)) : +#ifdef CWDEBUG + AIStateMachine(debug), +#endif + mInterval(0) { DoutEntering(dc::statemachine(mSMDebug), "AITimer(void) [" << (void*)this << "]"); } /** * @brief Set the interval after which the timer should expire. @@ -96,7 +100,7 @@ class AITimer : public AIStateMachine { protected: // Call finish() (or abort()), not delete. - /*virtual*/ ~AITimer() { DoutEntering(dc::statemachine, "~AITimer() [" << (void*)this << "]"); mFrameTimer.cancel(); } + /*virtual*/ ~AITimer() { DoutEntering(dc::statemachine(mSMDebug), "~AITimer() [" << (void*)this << "]"); mFrameTimer.cancel(); } // Handle initializing the object. /*virtual*/ void initialize_impl(void); diff --git a/indra/cwdebug/debug.h b/indra/cwdebug/debug.h index 4167813ea..d3c4e686b 100644 --- a/indra/cwdebug/debug.h +++ b/indra/cwdebug/debug.h @@ -144,6 +144,7 @@ extern LL_COMMON_API fake_channel const snapshot; #define CWDEBUG_MARKER 0 #define BACKTRACE do { } while(0) +#define CWD_ONLY(...) #endif // !DOXYGEN @@ -180,6 +181,7 @@ extern LL_COMMON_API fake_channel const snapshot; #include #define CWD_API __attribute__ ((visibility("default"))) +#define CWD_ONLY(...) __VA_ARGS__ //! Debug specific code. namespace debug { diff --git a/indra/llmessage/aicurleasyrequeststatemachine.cpp b/indra/llmessage/aicurleasyrequeststatemachine.cpp index 0437055f5..294f41586 100644 --- a/indra/llmessage/aicurleasyrequeststatemachine.cpp +++ b/indra/llmessage/aicurleasyrequeststatemachine.cpp @@ -250,10 +250,13 @@ void AICurlEasyRequestStateMachine::finish_impl(void) } } -AICurlEasyRequestStateMachine::AICurlEasyRequestStateMachine(void) : +AICurlEasyRequestStateMachine::AICurlEasyRequestStateMachine(CWD_ONLY(bool debug)) : +#ifdef CWDEBUG + AIStateMachine(debug), +#endif mTotalDelayTimeout(AIHTTPTimeoutPolicy::getDebugSettingsCurlTimeout().getTotalDelay()) { - Dout(dc::statemachine, "Calling AICurlEasyRequestStateMachine(void) [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]"); + Dout(dc::statemachine(mSMDebug), "Calling AICurlEasyRequestStateMachine(void) [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]"); AICurlInterface::Stats::AICurlEasyRequestStateMachine_count++; } @@ -264,7 +267,7 @@ void AICurlEasyRequestStateMachine::setTotalDelayTimeout(F32 totalDelayTimeout) AICurlEasyRequestStateMachine::~AICurlEasyRequestStateMachine() { - Dout(dc::statemachine, "Calling ~AICurlEasyRequestStateMachine() [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]"); + Dout(dc::statemachine(mSMDebug), "Calling ~AICurlEasyRequestStateMachine() [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]"); --AICurlInterface::Stats::AICurlEasyRequestStateMachine_count; } diff --git a/indra/llmessage/aicurleasyrequeststatemachine.h b/indra/llmessage/aicurleasyrequeststatemachine.h index 662efbe20..9bab7166b 100644 --- a/indra/llmessage/aicurleasyrequeststatemachine.h +++ b/indra/llmessage/aicurleasyrequeststatemachine.h @@ -52,7 +52,7 @@ // Construction of a AICurlEasyRequestStateMachine might throw AICurlNoEasyHandle. class AICurlEasyRequestStateMachine : public AIStateMachine, public AICurlEasyHandleEvents { public: - AICurlEasyRequestStateMachine(void); + AICurlEasyRequestStateMachine(CWD_ONLY(bool debug = false)); // Transparent access. AICurlEasyRequest mCurlEasyRequest; diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 55afaecee..80e68f608 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -1414,7 +1414,10 @@ void LLMeshUploadThread::preStart() AIMeshUpload::AIMeshUpload(LLMeshUploadThread::instance_list& data, LLVector3& scale, bool upload_textures, bool upload_skin, bool upload_joints, std::string const& upload_url, bool do_upload, LLHandle const& fee_observer, LLHandle const& upload_observer) : - mMeshUpload(new AIStateMachineThread), mWholeModelUploadURL(upload_url) +#ifdef CWDEBUG + AIStateMachine(false), +#endif + mMeshUpload(new AIStateMachineThread(CWD_ONLY(false))), mWholeModelUploadURL(upload_url) { mMeshUpload->thread_impl().init(data, scale, upload_textures, upload_skin, upload_joints, do_upload, fee_observer, upload_observer); } diff --git a/indra/newview/statemachine/aifetchinventoryfolder.cpp b/indra/newview/statemachine/aifetchinventoryfolder.cpp index d8e952c0d..840a0249e 100644 --- a/indra/newview/statemachine/aifetchinventoryfolder.cpp +++ b/indra/newview/statemachine/aifetchinventoryfolder.cpp @@ -155,7 +155,7 @@ void AIFetchInventoryFolder::multiplex_impl(state_type run_state) // Create the folder. mFolderUUID = gInventory.createNewCategory(mParentFolder, LLFolderType::FT_NONE, mFolderName); llassert_always(!mFolderUUID.isNull()); - Dout(dc::statemachine, "Created folder \"" << mFolderName << "\"."); + Dout(dc::statemachine(mSMDebug), "Created folder \"" << mFolderName << "\"."); mNeedNotifyObservers = true; } mCreated = true; diff --git a/indra/newview/statemachine/aifetchinventoryfolder.h b/indra/newview/statemachine/aifetchinventoryfolder.h index 2b10c0351..59419fc86 100644 --- a/indra/newview/statemachine/aifetchinventoryfolder.h +++ b/indra/newview/statemachine/aifetchinventoryfolder.h @@ -58,8 +58,12 @@ class AIFetchInventoryFolder : public AIStateMachine { bool mNeedNotifyObservers; public: - AIFetchInventoryFolder(void) : mCreate(false), mFetchContents(false), mExists(false), mCreated(false) - { Dout(dc::statemachine, "Calling AIFetchInventoryFolder constructor [" << (void*)this << "]"); } + AIFetchInventoryFolder(CWD_ONLY(bool debug = false)) : +#ifdef CWDEBUG + AIStateMachine(debug), +#endif + mCreate(false), mFetchContents(false), mExists(false), mCreated(false) + { Dout(dc::statemachine(mSMDebug), "Calling AIFetchInventoryFolder constructor [" << (void*)this << "]"); } /** * @brief Fetch an inventory folder by name, optionally creating it. @@ -132,7 +136,7 @@ class AIFetchInventoryFolder : public AIStateMachine { protected: // Call finish() (or abort()), not delete. - /*virtual*/ ~AIFetchInventoryFolder() { Dout(dc::statemachine, "Calling ~AIFetchInventoryFolder() [" << (void*)this << "]"); } + /*virtual*/ ~AIFetchInventoryFolder() { Dout(dc::statemachine(mSMDebug), "Calling ~AIFetchInventoryFolder() [" << (void*)this << "]"); } // Handle initializing the object. /*virtual*/ void initialize_impl(void); diff --git a/indra/newview/statemachine/aifilepicker.cpp b/indra/newview/statemachine/aifilepicker.cpp index 636fc2343..6754142e4 100644 --- a/indra/newview/statemachine/aifilepicker.cpp +++ b/indra/newview/statemachine/aifilepicker.cpp @@ -60,7 +60,11 @@ char const* AIFilePicker::state_str_impl(state_type run_state) const return "UNKNOWN STATE"; } -AIFilePicker::AIFilePicker(void) : mPluginManager(NULL), mCanceled(false) +AIFilePicker::AIFilePicker(CWD_ONLY(bool debug)) : +#ifdef CWDEBUG + AIStateMachine(debug), +#endif + mPluginManager(NULL), mCanceled(false) { } diff --git a/indra/newview/statemachine/aifilepicker.h b/indra/newview/statemachine/aifilepicker.h index cd1c7fe86..9ef02017b 100644 --- a/indra/newview/statemachine/aifilepicker.h +++ b/indra/newview/statemachine/aifilepicker.h @@ -168,7 +168,7 @@ public: public: // The derived class must have a default constructor. - AIFilePicker(void); + AIFilePicker(CWD_ONLY(bool debug = false)); // Create a dynamically created AIFilePicker object. static AIFilePicker* create(void) { AIFilePicker* filepicker = new AIFilePicker; return filepicker; }