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
+