From ea0a5c521f47353b039a4098f4d2d073e1ec53f0 Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Mon, 6 Feb 2012 22:34:34 +0100 Subject: [PATCH 1/4] Fix for libcwd --- indra/llinventory/llsaleinfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llinventory/llsaleinfo.cpp b/indra/llinventory/llsaleinfo.cpp index b7afb28ad..e51e35e6e 100644 --- a/indra/llinventory/llsaleinfo.cpp +++ b/indra/llinventory/llsaleinfo.cpp @@ -30,8 +30,8 @@ * $/LicenseInfo$ */ -#include #include "linden_common.h" +#include #include "llsaleinfo.h" From cad0597524c73ba0c9253fab2d0a3ee32a1f3b49 Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Fri, 10 Feb 2012 00:23:20 +0100 Subject: [PATCH 2/4] Allow AIStateMachine::run() to be called from callback functions. This was already documented as working, but turned out not to work. Now one can call any of the run(...) functions to guarantee a restart of the statemachine. Using run() without parameters from a callback function re-uses the old callback information. Introduces a new enum AIStateMachine::active_type that keeps track of on which list the statemachine resides, if any. This was necessary because run() calls cont() which now can be called while the statemachine is already on the active list, so it needs to know more than just if it's on the continued_statemachines list or not. --- indra/newview/statemachine/aistatemachine.cpp | 111 ++++++++++++------ indra/newview/statemachine/aistatemachine.h | 28 +++-- 2 files changed, 97 insertions(+), 42 deletions(-) diff --git a/indra/newview/statemachine/aistatemachine.cpp b/indra/newview/statemachine/aistatemachine.cpp index cbe261cc4..bb166a9b5 100644 --- a/indra/newview/statemachine/aistatemachine.cpp +++ b/indra/newview/statemachine/aistatemachine.cpp @@ -92,17 +92,34 @@ void AIStateMachine::updateSettings(void) void AIStateMachine::run(AIStateMachine* parent, state_type new_parent_state, bool abort_parent) { DoutEntering(dc::statemachine, "AIStateMachine::run(" << (void*)parent << ", " << (parent ? parent->state_str(new_parent_state) : "NA") << ", " << abort_parent << ") [" << (void*)this << "]"); - // Must be the first time we're being run. - llassert(!mParent); - llassert(!mCallback); + // Must be the first time we're being run, or we must be called from a callback function. + llassert(!mParent || mState == bs_callback); + llassert(!mCallback || mState == bs_callback); // Can only be run when in this state. - llassert(mState == bs_initialize); - // If a parent is provided, it must be running. - llassert(!parent || parent->mState == bs_run); + llassert(mState == bs_initialize || mState == bs_callback); - mParent = parent; - mNewParentState = new_parent_state; - mAbortParent = abort_parent; + // Allow NULL to be passed as parent to signal that we want to reuse the old one. + if (parent) + { + mParent = parent; + // In that case remove any old callback! + if (mCallback) + { + delete mCallback; + mCallback = NULL; + } + + mNewParentState = new_parent_state; + mAbortParent = abort_parent; + } + + // If abort_parent is requested then a parent must be provided. + llassert(!abort_parent || mParent); + // If a parent is provided, it must be running. + llassert(!mParent || mParent->mState == bs_run); + + // Mark that run() has been called, in case we're being called from a callback function. + mState = bs_initialize; cont(); } @@ -110,14 +127,25 @@ void AIStateMachine::run(AIStateMachine* parent, state_type new_parent_state, bo void AIStateMachine::run(callback_type::signal_type::slot_type const& slot) { DoutEntering(dc::statemachine, "AIStateMachine::run() [" << (void*)this << "]"); - // Must be the first time we're being run. - llassert(!mParent); - llassert(!mCallback); + // Must be the first time we're being run, or we must be called from a callback function. + llassert(!mParent || mState == bs_callback); + llassert(!mCallback || mState == bs_callback); // Can only be run when in this state. - llassert(mState == bs_initialize); + llassert(mState == bs_initialize || mState == bs_callback); + + // Clean up any old callbacks. + mParent = NULL; + if (mCallback) + { + delete mCallback; + mCallback = NULL; + } mCallback = new callback_type(slot); + // Mark that run() has been called, in case we're being called from a callback function. + mState = bs_initialize; + cont(); } @@ -134,17 +162,18 @@ void AIStateMachine::cont(void) DoutEntering(dc::statemachine, "AIStateMachine::cont() [" << (void*)this << "]"); llassert(mIdle); mIdle = false; - if (mQueued) - return; - AIWriteAccess cscm_w(continued_statemachines_and_calling_mainloop); - cscm_w->continued_statemachines.push_back(this); - if (!cscm_w->calling_mainloop) + if (mActive == as_idle) { - Dout(dc::statemachine, "Adding AIStateMachine::mainloop to gIdleCallbacks"); - cscm_w->calling_mainloop = true; - gIdleCallbacks.addFunction(&AIStateMachine::mainloop); + AIWriteAccess cscm_w(continued_statemachines_and_calling_mainloop); + cscm_w->continued_statemachines.push_back(this); + if (!cscm_w->calling_mainloop) + { + Dout(dc::statemachine, "Adding AIStateMachine::mainloop to gIdleCallbacks"); + cscm_w->calling_mainloop = true; + gIdleCallbacks.addFunction(&AIStateMachine::mainloop); + } + mActive = as_queued; } - mQueued = true; } void AIStateMachine::set_state(state_type state) @@ -194,32 +223,44 @@ void AIStateMachine::finish(void) if (mAborted && mAbortParent) { mParent->abort(); + mParent = NULL; } else { mParent->set_state(mNewParentState); } } - mParent = NULL; } - // Set this already to bs_initialize now, so that (bool)*this evaluates to true. - mState = bs_initialize; + // After this (bool)*this evaluates to true and we can call the callback, which then is allowed to call run(). + mState = bs_callback; if (mCallback) { - mCallback->callback(!mAborted); // This can/may call kill(), in which case the whole AIStateMachine will be deleted from the mainloop. - delete mCallback; - mCallback = NULL; + // This can/may call kill() that sets mState to bs_kill and in which case the whole AIStateMachine + // will be deleted from the mainloop, or it may call run() that sets mState is set to bs_initialize + // and might change or reuse mCallback or mParent. + mCallback->callback(!mAborted); + if (mState != bs_initialize) + { + delete mCallback; + mCallback = NULL; + mParent = NULL; + } } - // Restore the request for deletion if we weren't started again from the callback. - if (default_delete && mState == bs_initialize) - mState = bs_killed; + else + { + // Not restarted by callback. Allow run() to be called later on. + mParent = NULL; + } + // Fix the final state. + if (mState == bs_callback) + mState = default_delete ? bs_killed : bs_initialize; } void AIStateMachine::kill(void) { // Should only be called from finish(). - llassert(mIdle && (mState == bs_initialize || mState == bs_finish)); - if (mState == bs_initialize) + llassert(mIdle && (mState == bs_callback || mState == bs_finish)); + if (mState == bs_callback && mActive == as_idle) { // Bump the statemachine onto the active statemachine list, or else it won't be deleted. cont(); @@ -239,6 +280,7 @@ char const* AIStateMachine::state_str(state_type state) AI_CASE_RETURN(bs_run); AI_CASE_RETURN(bs_abort); AI_CASE_RETURN(bs_finish); + AI_CASE_RETURN(bs_callback); AI_CASE_RETURN(bs_killed); } } @@ -300,7 +342,7 @@ void AIStateMachine::mainloop(void*) nonempty = true; active_statemachines.push_back(QueueElement(*iter)); Dout(dc::statemachine, "Adding " << (void*)*iter << " to active_statemachines"); - (*iter)->mQueued = false; + (*iter)->mActive = as_active; } if (nonempty) AIWriteAccess(cscm_r)->continued_statemachines.clear(); @@ -337,6 +379,7 @@ void AIStateMachine::mainloop(void*) if (statemachine.mIdle) { Dout(dc::statemachine, "Erasing " << (void*)&statemachine << " from active_statemachines"); + statemachine.mActive = as_idle; iter = active_statemachines.erase(iter); if (statemachine.mState == bs_killed) { diff --git a/indra/newview/statemachine/aistatemachine.h b/indra/newview/statemachine/aistatemachine.h index 0c905668c..67933d319 100644 --- a/indra/newview/statemachine/aistatemachine.h +++ b/indra/newview/statemachine/aistatemachine.h @@ -75,10 +75,13 @@ // Abort | | Calls abort_impl(). // | | | // v v | -// Finish | Calls finish_impl(), which may call kill() and/or the callback -// | | | function passed to run(), if any, which may call kill() and/or run(). -// | `-------' -// v +// Finish | Calls finish_impl() (which may call kill()) or +// | | | the callback function passed to run(), if any, +// | v | +// | Callback | which may call kill() and/or run(). +// | | | | +// | | `-----' +// v v // Killed Delete the statemachine (all statemachines must be allocated with new). // // Each state causes corresponding code to be called. @@ -165,6 +168,8 @@ // // Should cleanup whatever init_impl() did, or any of the // states of the object where multiplex_impl() calls finish(). +// Call kill() from here to make that the default behavior +// (state machine is deleted unless the callback calls run()). // // virtual char const* state_str_impl(state_type run_state); // @@ -177,8 +182,15 @@ class AIStateMachine { bs_run, bs_abort, bs_finish, + bs_callback, bs_killed }; + //! The type of mActive + enum active_type { + as_idle, // State machine is on neither list. + as_queued, // State machine is on continued_statemachines list. + as_active // State machine is on active_statemachines list. + }; public: typedef U32 state_type; //!< The type of mRunState @@ -192,7 +204,7 @@ class AIStateMachine { base_state_type mState; //!< State of the base class. bool mIdle; //!< True if this state machine is not running. bool mAborted; //!< True after calling abort() and before calling run(). - bool mQueued; //!< True when the statemachine is queued to be added back to the active list. + active_type mActive; //!< Whether statemachine is idle, queued to be added to the active list, or already on the active list. S64 mSleep; //!< Non-zero while the state machine is sleeping. // Callback facilities. @@ -220,11 +232,11 @@ class AIStateMachine { public: //! Create a non-running state machine. - AIStateMachine(void) : mState(bs_initialize), mIdle(true), mAborted(true), mQueued(false), mSleep(0), mParent(NULL), mCallback(NULL) { updateSettings(); } + AIStateMachine(void) : mState(bs_initialize), mIdle(true), mAborted(true), mActive(as_idle), mSleep(0), mParent(NULL), mCallback(NULL) { updateSettings(); } protected: //! The user should call 'kill()', not delete a AIStateMachine (derived) directly. - virtual ~AIStateMachine() { llassert(mState == bs_killed && !mQueued); } + virtual ~AIStateMachine() { llassert(mState == bs_killed && mActive == as_idle); } public: //! Halt the state machine until cont() is called. @@ -319,7 +331,7 @@ class AIStateMachine { // Use some safebool idiom (http://www.artima.com/cppsource/safebool.html) rather than operator bool. typedef state_type AIStateMachine::* const bool_type; //! Return true if state machine successfully finished. - operator bool_type() const { return (mState == bs_initialize && !mAborted) ? &AIStateMachine::mRunState : 0; } + operator bool_type() const { return ((mState == bs_initialize || mState == bs_callback) && !mAborted) ? &AIStateMachine::mRunState : 0; } //! Return a stringified state, for debugging purposes. char const* state_str(state_type state); From d4591828c87d4e119fa753f4d05bb314d89eb9e0 Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Fri, 10 Feb 2012 01:37:43 +0100 Subject: [PATCH 3/4] Finished AIFrameTimer. Added documentation. Added AIFrameTimer::isRunning. Fixed a bug: mCallback was deleted before it was used. --- indra/llcommon/aiframetimer.cpp | 34 +++++++++++++++++++++++++-- indra/llcommon/aiframetimer.h | 41 ++++++++++++++++++++++++--------- 2 files changed, 62 insertions(+), 13 deletions(-) diff --git a/indra/llcommon/aiframetimer.cpp b/indra/llcommon/aiframetimer.cpp index cb57bb701..3c102a9c7 100644 --- a/indra/llcommon/aiframetimer.cpp +++ b/indra/llcommon/aiframetimer.cpp @@ -27,8 +27,38 @@ * - Initial version, written by Aleric Inglewood @ SL */ -#include "linden_common.h" +// An AIFrameTimer object provides a callback API for timer events. +// +// Typical usage: +// +// // Any thread. +// AIFrameTimer timer; +// +// ... +// // Any thread (after successful construction is guaranteed). +// timer.create(5.5, boost::bind(&the_callback, )); // Call the_callback() in 5.5 seconds. +// +// The callback function is always called by the main thread and should therefore +// be light weight. +// +// If timer.cancel() is called before the timer expires, then the callback +// function isn't called. If cancel() is called by another thread than the +// main thread, then it is possible that the callback function is called +// even while still inside cancel(), but as soon as cancel() returned it +// is guarenteed that the callback function won't be called anymore. +// Hence, if the callback function is a member of some object then +// cancel() must be called before the destruction of that object (ie from +// it's destructor). Calling cancel() multiple times is not a problem. +// Note: if cancel() is called while the callback function is being +// called then cancel() will block until the callback function returned. +// +// The timer object can be reused (by calling create() again), but +// only after either the callback function was called, or after cancel() +// returned. Most notably, you can call create() again from inside the +// callback function to "restart" the timer. +// +#include "linden_common.h" #include "aiframetimer.h" static F64 const NEVER = 1e16; // 317 million years. @@ -115,7 +145,7 @@ void AIFrameTimer::handleExpiration(F64 current_frame_time) // function here because the trylock fails. // // Assuming the main thread arrived here, there are two possible states - // for the other thread that tries to delete the call back function, + // for the other thread that tries to delete the callback function, // right after calling the cancel() function too: // // 1. It hasn't obtained the first lock yet, we obtain the handle.mMutex diff --git a/indra/llcommon/aiframetimer.h b/indra/llcommon/aiframetimer.h index 3b997560d..cb50caec5 100644 --- a/indra/llcommon/aiframetimer.h +++ b/indra/llcommon/aiframetimer.h @@ -61,20 +61,37 @@ class LL_COMMON_API AIFrameTimer // See aiframetimer.cpp for more notes. class AIRunningFrameTimer { private: - F64 mExpire; // Time at which the timer expires, in seconds since application start (compared to LLFrameTimer::sFrameTime). - Signal* mCallback; - AIFrameTimer* mTimer; + F64 mExpire; // Time at which the timer expires, in seconds since application start (compared to LLFrameTimer::sFrameTime). + AIFrameTimer* mTimer; // The actual timer. + // Can be mutable, since only the mExpire is used for ordering this object in the multiset AIFrameTimer::sTimerList. + mutable Signal* mCallback; // Pointer to callback struct, or NULL when the object wasn't added to sTimerList yet. public: - AIRunningFrameTimer(F64 expiration, AIFrameTimer* timer) : mExpire(LLFrameTimer::getElapsedSeconds() + expiration), mCallback(new Signal), mTimer(timer) { } + AIRunningFrameTimer(F64 expiration, AIFrameTimer* timer) : mExpire(LLFrameTimer::getElapsedSeconds() + expiration), mCallback(NULL), mTimer(timer) { } ~AIRunningFrameTimer() { delete mCallback; } - void init(signal_type::slot_type const& slot) const { mCallback->mSignal.connect(slot); } + // This function is called after the final object was added to sTimerList (where it is initialized in-place). + void init(signal_type::slot_type const& slot) const + { + // We may only call init() once. + llassert(!mCallback); + mCallback = new Signal; + mCallback->mSignal.connect(slot); + } + + // Order AIFrameTimer::sTimerList so that the timer that expires first is up front. friend bool operator<(AIRunningFrameTimer const& ft1, AIRunningFrameTimer const& ft2) { return ft1.mExpire < ft2.mExpire; } void do_callback(void) const { mCallback->mSignal(); } F64 expiration(void) const { return mExpire; } AIFrameTimer* getTimer(void) const { return mTimer; } + +#if LL_DEBUG + // May not copy this object after it was initialized. + AIRunningFrameTimer(AIRunningFrameTimer const& running_frame_timer) : + mExpire(running_frame_timer.mExpire), mCallback(running_frame_timer.mCallback), mTimer(running_frame_timer.mTimer) + { llassert(!mCallback); } +#endif }; typedef std::multiset timer_list_type; @@ -98,12 +115,12 @@ class LL_COMMON_API AIFrameTimer // Actual initialization used by AIFrameTimer::create. void init(timer_list_type::iterator const& running_timer, signal_type::slot_type const& slot) - { - // Locking AIFrameTimer::sMutex is not neccessary here, because we're creating - // the object and no other thread knows of mRunningTimer at this point. - mRunningTimer = running_timer; - mRunningTimer->init(slot); - } + { + // Locking AIFrameTimer::sMutex is not neccessary here, because we're creating + // the object and no other thread knows of mRunningTimer at this point. + mRunningTimer = running_timer; + mRunningTimer->init(slot); + } private: // LLMutex has no assignment operator. @@ -129,6 +146,8 @@ class LL_COMMON_API AIFrameTimer void create(F64 expiration, signal_type::slot_type const& slot); void cancel(void); + bool isRunning(void) const { bool running; sMutex.lock(); running = mHandle.mRunningTimer != sTimerList.end(); sMutex.unlock(); return running; } + protected: static void handleExpiration(F64 current_frame_time); }; From e0b21b08fa205785c52fdb21b8fe7e09791330e8 Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Fri, 10 Feb 2012 02:01:14 +0100 Subject: [PATCH 4/4] Add AITimer and AIPersistentTimer state machines. For use inside other state machines that need timer events. Wraps AIFrameTimer. --- indra/newview/statemachine/CMakeLists.txt | 2 + indra/newview/statemachine/aitimer.cpp | 96 ++++++++++++++++++ indra/newview/statemachine/aitimer.h | 118 ++++++++++++++++++++++ 3 files changed, 216 insertions(+) create mode 100644 indra/newview/statemachine/aitimer.cpp create mode 100644 indra/newview/statemachine/aitimer.h diff --git a/indra/newview/statemachine/CMakeLists.txt b/indra/newview/statemachine/CMakeLists.txt index a95431b0f..86f19107e 100644 --- a/indra/newview/statemachine/CMakeLists.txt +++ b/indra/newview/statemachine/CMakeLists.txt @@ -39,6 +39,7 @@ set(statemachine_SOURCE_FILES aifilepicker.cpp aifetchinventoryfolder.cpp aievent.cpp + aitimer.cpp ) set(statemachine_HEADER_FILES @@ -48,6 +49,7 @@ set(statemachine_HEADER_FILES aidirpicker.h aifetchinventoryfolder.h aievent.h + aitimer.h ) set_source_files_properties(${statemachine_HEADER_FILES} diff --git a/indra/newview/statemachine/aitimer.cpp b/indra/newview/statemachine/aitimer.cpp new file mode 100644 index 000000000..5a37a6991 --- /dev/null +++ b/indra/newview/statemachine/aitimer.cpp @@ -0,0 +1,96 @@ +/** + * @file aitimer.cpp + * @brief Implementation of AITimer + * + * Copyright (c) 2012, 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. + * + * 07/02/2012 + * Initial version, written by Aleric Inglewood @ SL + */ + +#include "linden_common.h" +#include "aitimer.h" + +enum timer_state_type { + AITimer_start = AIStateMachine::max_state, + AITimer_expired +}; + +char const* AITimer::state_str_impl(state_type run_state) const +{ + switch(run_state) + { + AI_CASE_RETURN(AITimer_start); + AI_CASE_RETURN(AITimer_expired); + } + return "UNKNOWN STATE"; +} + +void AITimer::initialize_impl(void) +{ + llassert(!mFrameTimer.isRunning()); + set_state(AITimer_start); +} + +void AITimer::expired(void) +{ + set_state(AITimer_expired); +} + +void AITimer::multiplex_impl(void) +{ + switch (mRunState) + { + case AITimer_start: + { + mFrameTimer.create(mInterval, boost::bind(&AITimer::expired, this)); + idle(); + break; + } + case AITimer_expired: + { + finish(); + break; + } + } +} + +void AITimer::abort_impl(void) +{ + mFrameTimer.cancel(); +} + +void AITimer::finish_impl(void) +{ + // Kill object by default. + // This can be overridden by calling run() from the callback function. + kill(); +} + +void AIPersistentTimer::finish_impl(void) +{ + // Don't kill object by default. + if (aborted()) + kill(); + // Callback function should always call kill() or run(). +} diff --git a/indra/newview/statemachine/aitimer.h b/indra/newview/statemachine/aitimer.h new file mode 100644 index 000000000..06d3e2ef4 --- /dev/null +++ b/indra/newview/statemachine/aitimer.h @@ -0,0 +1,118 @@ +/** + * @file aitimer.h + * @brief Generate a timer event + * + * Copyright (c) 2012, 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. + * + * 07/02/2012 + * Initial version, written by Aleric Inglewood @ SL + */ + +#ifndef AITIMER_H +#define AITIMER_H + +#include "aistatemachine.h" +#include "aiframetimer.h" + +// A timer state machine. +// +// Before calling run(), call setInterval() to pass needed parameters. +// +// When the state machine finishes it calls the callback, use parameter _1, +// (success) to check whether or not the statemachine actually timed out or +// was cancelled. The boolean is true when it expired and false if the +// state machine was aborted. +// +// Objects of this type can be reused multiple times, see +// also the documentation of AIStateMachine. +// +// Typical usage: +// +// AITimer* timer = new AITimer; +// +// timer->setInterval(5.5); // 5.5 seconds time out interval. +// timer->run(...); // Start timer and pass callback; see AIStateMachine. +// +// The default behavior is to call the callback and then delete the AITimer object. +// One can call run() again from the callback function to get a repeating expiration. +// You can call run(...) with parameters too, but using run() without parameters will +// just reuse the old ones (call the same callback). +// +class AITimer : public AIStateMachine { + private: + AIFrameTimer mFrameTimer; //!< The actual timer that this object wraps. + 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 << "]"); } + + /** + * @brief Set the interval after which the timer should expire. + * + * @param interval Amount of time in seconds before the timer will expire. + * @param True if the timer should be deleted after it expires; false means it will keep firing at regular intervals. + * + * Call abort() at any time to stop the timer (and delete the AITimer object). + */ + void setInterval(F64 interval) { mInterval = interval; } + + /** + * @brief Get the expiration interval. + * + * @returns expiration interval in seconds. + */ + F64 getInterval(void) const { return mInterval; } + + protected: + // Call finish() (or abort()), not delete. + /*virtual*/ ~AITimer() { DoutEntering(dc::statemachine, "~AITimer() [" << (void*)this << "]"); mFrameTimer.cancel(); } + + // 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: + // This is the callback for mFrameTimer. + void expired(void); +}; + +// Same as above but does not delete itself automatically by default after use. +// Call kill() on it yourself (from the callback function) when you're done with it! +class AIPersistentTimer : public AITimer { + protected: + // Handle cleaning up from initialization (or post abort) state. + /*virtual*/ void finish_impl(void); +}; + +#endif