Added AIStateMachineThread

This commit is contained in:
Aleric Inglewood
2013-01-26 19:18:41 +01:00
parent 6dc4a60724
commit c528a15e95
3 changed files with 376 additions and 0 deletions

View File

@@ -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
)

View File

@@ -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 <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.
*
* 23/01/2013
* Initial version, written by Aleric Inglewood @ SL
*/
#include "linden_common.h"
#include "aistatemachinethread.h"
class StateMachineThread : public LLThread {
private:
LLPointer<AIThreadImpl> 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<THREAD_IMPL>, LLPointer<THREAD_IMPL>, 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;
}

View File

@@ -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 <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.
*
* 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<HelloWorldThread> 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<AIStateMachineThreadBase*> StateMachineThread_wat;
AIThreadSafeSimpleDC<AIStateMachineThreadBase*> 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<typename THREAD_IMPL>
struct AIStateMachineThread : public LLPointer<THREAD_IMPL>, public AIStateMachineThreadBase {
// Constructor.
AIStateMachineThread(void) :
LLPointer<THREAD_IMPL>(new THREAD_IMPL(this)),
AIStateMachineThreadBase(LLPointer<THREAD_IMPL>::get())
{ }
};
#endif