From c528a15e957d31673d26afd0fa5a95042f68c90d Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Sat, 26 Jan 2013 19:18:41 +0100 Subject: [PATCH] Added AIStateMachineThread --- indra/aistatemachine/CMakeLists.txt | 2 + indra/aistatemachine/aistatemachinethread.cpp | 157 +++++++++++++ indra/aistatemachine/aistatemachinethread.h | 217 ++++++++++++++++++ 3 files changed, 376 insertions(+) create mode 100644 indra/aistatemachine/aistatemachinethread.cpp create mode 100644 indra/aistatemachine/aistatemachinethread.h diff --git a/indra/aistatemachine/CMakeLists.txt b/indra/aistatemachine/CMakeLists.txt index 18ba3c034..6db29a007 100644 --- a/indra/aistatemachine/CMakeLists.txt +++ b/indra/aistatemachine/CMakeLists.txt @@ -19,6 +19,7 @@ include_directories( set(aistatemachine_SOURCE_FILES aistatemachine.cpp + aistatemachinethread.cpp aitimer.cpp ) @@ -26,6 +27,7 @@ set(aistatemachine_HEADER_FILES CMakeLists.txt aistatemachine.h + aistatemachinethread.h aitimer.h ) diff --git a/indra/aistatemachine/aistatemachinethread.cpp b/indra/aistatemachine/aistatemachinethread.cpp new file mode 100644 index 000000000..881036d0c --- /dev/null +++ b/indra/aistatemachine/aistatemachinethread.cpp @@ -0,0 +1,157 @@ +/** + * @file aistatemachinethread.cpp + * @brief Implementation of AIStateMachineThread + * + * 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. + * + * 23/01/2013 + * Initial version, written by Aleric Inglewood @ SL + */ + +#include "linden_common.h" +#include "aistatemachinethread.h" + +class StateMachineThread : public LLThread { + private: + LLPointer mImpl; + bool mNeedCleanup; + public: + StateMachineThread(AIThreadImpl* impl) : LLThread("StateMachineThread"), mImpl(impl) { } + protected: + /*virtual*/ void run(void) + { + mNeedCleanup = !mImpl->done(mImpl->run()); + } + /*virtual*/ void terminated(void) + { + if (mNeedCleanup) + { + // The state machine that started us has disappeared! Clean up ourselves. + mStatus = STOPPED; // Stop LLThread::shutdown from blocking a whole minute and then calling apr_thread_exit to never return! + // This is OK now because nobody is watching us and having set the status to STOPPED didn't change anything really. + // Normally is can cause the LLThread to be deleted directly after by the main thread. + delete this; + } + } +}; + +void AIStateMachineThreadBase::initialize_impl(void) +{ + mThread = NULL; + mAbort = false; + set_state(start_thread); +} + +void AIStateMachineThreadBase::multiplex_impl(void) +{ + switch(mRunState) + { + case start_thread: + // TODO: Implement thread pools. For now just start a new thread. + mThread = new StateMachineThread(mImpl); + // Set next state. + set_state(wait_stopped); + idle(wait_stopped); // Wait till the thread returns. + mThread->start(); + break; + case wait_stopped: + if (!mThread->isStopped()) + break; + // We're done! + mImpl = NULL; + delete mThread; + mThread = NULL; + if (mAbort) + abort(); + else + finish(); + break; + } +} + +void AIStateMachineThreadBase::abort_impl(void) +{ + // If this AIStateMachineThreadBase still exists then the first base class of + // AIStateMachineThread, LLPointer, also still exists + // and therefore mImpl is valid (unless we set it NULL above). + if (mImpl) + { + // We can only get here if the statemachine was aborted by it's parent, + // in that case we'll never reach the "We're done!" above, so this + // call also signals that the thread has to clean up itself. + mImpl->abort(); + } +} + +void AIStateMachineThreadBase::finish_impl(void) +{ + if (mThread) + { + if (!mThread->isStopped()) + { + // Lets make sure we're not flooded with this. + llwarns << "Thread state machine aborted while the thread is still running. That is a waste of CPU and should be avoided." << llendl; + // In fact, this should only ever happen when the state machine was aborted! + llassert(aborted()); + if (!aborted()) + { + // Avoid a call back to us. + mImpl->abort(); + } + mThread->setQuitting(); // Try to signal the thread that is can abort. + } + mThread = NULL; + } +} + +char const* AIStateMachineThreadBase::state_str_impl(state_type run_state) const +{ + switch(run_state) + { + AI_CASE_RETURN(start_thread); + AI_CASE_RETURN(wait_stopped); + } + return "UNKNOWN STATE"; +} + +// AIStateMachineThread THREAD + +bool AIThreadImpl::done(bool result) +{ + StateMachineThread_wat state_machine_thread_w(mStateMachineThread); + AIStateMachineThreadBase* state_machine_thread = *state_machine_thread_w; + bool state_machine_running = state_machine_thread; + if (state_machine_running) + { + // If state_machine_thread is non-NULL, then AIStateMachineThreadBase::abort_impl was never called, + // which means the state machine still exists. In fact, it should be in the waiting() state. + // The only other way it could possibly have been destructed is when finished() was called + // while the thread was still running, which would be quite illegal and not possible when + // the statemachine is actually waiting as it should. + llassert(state_machine_thread->waiting()); + state_machine_thread->schedule_abort(!result); + state_machine_thread->cont(); + } + return state_machine_running; +} + diff --git a/indra/aistatemachine/aistatemachinethread.h b/indra/aistatemachine/aistatemachinethread.h new file mode 100644 index 000000000..1f9312bdd --- /dev/null +++ b/indra/aistatemachine/aistatemachinethread.h @@ -0,0 +1,217 @@ +/** + * @file aistatemachinethread.h + * @brief Run code in a thread. + * + * 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. + * + * 23/01/2013 + * Initial version, written by Aleric Inglewood @ SL + */ + +#ifndef AISTATEMACHINETHREAD_H +#define AISTATEMACHINETHREAD_H + +#include "aistatemachine.h" +#include "llthread.h" +#include "aithreadsafe.h" + +#ifdef EXAMPLE_CODE // undefined + +class HelloWorldThread : public AIThreadImpl { + private: + bool mStdErr; // input + bool mSuccess; // output + + public: + // Constructor. + HelloWorldThread(AIStateMachineThreadBase* state_machine_thread) : + AIThreadImpl(state_machine_thread), mStdErr(false), mSuccess(false) { } // MAIN THREAD + + // Some initialization function (if needed). + void init(bool err) { mStdErr = err; } // MAIN THREAD + // Read back output. + bool successful(void) const { return mSuccess; } + + // Mandatory signature. + /*virtual*/ bool run(void) // NEW THREAD + { + if (mStdErr) + std::cerr << "Hello world" << std::endl; + else + std::cout << "Hello world" << std::endl; + mSuccess = true; + return true; // true = finish, false = abort. + } +}; + +// The states of this state machine. +enum hello_world_state_type { + HelloWorld_start = AIStateMachine::max_state, + HelloWorld_done +}; + +// The statemachine class (this is almost a template). +class HelloWorld : public AIStateMachine { + private: + AIStateMachineThread mHelloWorld; + bool mErr; + + public: + HelloWorld() : mErr(false) { } + + // Print to stderr or stdout? + void init(bool err) { mErr = err; } + + protected: + // Call finish() (or abort()), not delete. + /*virtual*/ ~HelloWorld() { } + + // Handle initializing the object. + /*virtual*/ void initialize_impl(void); + + // Handle mRunState. + /*virtual*/ void multiplex_impl(void); + + // Handle aborting from current bs_run state. + /*virtual*/ void abort_impl(void) { } + + // Handle cleaning up from initialization (or post abort) state. + /*virtual*/ void finish_impl(void) + { + // Kill object by default. + // This can be overridden by calling run() from the callback function. + kill(); + } + + // Implemenation of state_str for run states. + /*virtual*/ char const* state_str_impl(state_type run_state) const + { + switch(run_state) + { + AI_CASE_RETURN(HelloWorld_start); + AI_CASE_RETURN(HelloWorld_done); + } + return "UNKNOWN STATE"; + } +}; + +// The actual implementation of this statemachine starts here! + +void HelloWorld::initialize_impl(void) +{ + mHelloWorld->init(mErr); // Initialize the thread object. + set_state(HelloWorld_start); +} + +void HelloWorld::multiplex_impl(void) +{ + switch (mRunState) + { + case HelloWorld_start: + { + mHelloWorld.run(this, HelloWorld_done); // Run HelloWorldThread and set the state of 'this' to HelloWorld_done when finished. + // This statemachine is already idle() here (set to idle by the above call). + break; + } + case HelloWorld_done: + { + // We're done. Lets also abort when the thread reported no success. + if (mHelloWorld->successful()) // Read output/result of thread object. + finish(); + else + abort(); + break; + } + } +} + +#endif // EXAMPLE CODE + +class AIStateMachineThreadBase; + +// Derive from this to implement the code that must run in another thread. +class AIThreadImpl : public LLThreadSafeRefCount { + private: + typedef AIAccess StateMachineThread_wat; + AIThreadSafeSimpleDC mStateMachineThread; + + protected: + AIThreadImpl(AIStateMachineThreadBase* state_machine_thread) : mStateMachineThread(state_machine_thread) { } + + public: + virtual bool run(void) = 0; + bool done(bool result); + void abort(void) { *StateMachineThread_wat(mStateMachineThread) = NULL; } +}; + +// The base class for statemachine threads. +class AIStateMachineThreadBase : public AIStateMachine { + private: + // The states of this state machine. + enum thread_state_type { + start_thread = AIStateMachine::max_state, // Start the thread (if necessary create it first). + wait_stopped // Wait till the thread is stopped. + }; + + protected: + AIStateMachineThreadBase(AIThreadImpl* impl) : mImpl(impl) { } + + private: + // Handle initializing the object. + /*virtual*/ void initialize_impl(void); + + // Handle mRunState. + /*virtual*/ void multiplex_impl(void); + + // Handle aborting from current bs_run state. + /*virtual*/ void abort_impl(void); + + // Handle cleaning up from initialization (or post abort) state. + /*virtual*/ void finish_impl(void); + + // Implemenation of state_str for run states. + /*virtual*/ char const* state_str_impl(state_type run_state) const; + + private: + LLThread* mThread; // The thread that the code is run in. + AIThreadImpl* mImpl; // Pointer to the implementation code that needs to be run in the thread. + bool mAbort; // (Inverse of) return value of AIThreadImpl::run(). Only valid in state wait_stopped. + + public: + void schedule_abort(bool do_abort) { mAbort = do_abort; } + +}; + +// The state machine that runs T::run() in a thread. +// THREAD_IMPL Must be derived from AIThreadImpl. +template +struct AIStateMachineThread : public LLPointer, public AIStateMachineThreadBase { + // Constructor. + AIStateMachineThread(void) : + LLPointer(new THREAD_IMPL(this)), + AIStateMachineThreadBase(LLPointer::get()) + { } +}; + +#endif +