Created aistatemachine library and moved files.
This commit is contained in:
@@ -30,7 +30,7 @@ include(LLInventory)
|
||||
include(LLMath)
|
||||
include(LLMessage)
|
||||
include(LLPlugin)
|
||||
include(AIStateMachine)
|
||||
include(StateMachine)
|
||||
include(LLPrimitive)
|
||||
include(LLRender)
|
||||
include(LLUI)
|
||||
@@ -54,6 +54,7 @@ endif (WINDOWS)
|
||||
|
||||
include_directories(
|
||||
${CMAKE_SOURCE_DIR}/newview
|
||||
${STATEMACHINE_INCLUDE_DIRS}
|
||||
${DBUSGLIB_INCLUDE_DIRS}
|
||||
${HUNSPELL_INCLUDE_DIR}
|
||||
${ELFIO_INCLUDE_DIR}
|
||||
@@ -152,7 +153,6 @@ set(viewer_SOURCE_FILES
|
||||
llconfirmationmanager.cpp
|
||||
llconsole.cpp
|
||||
llcontainerview.cpp
|
||||
llcurlrequest.cpp
|
||||
llcurrencyuimanager.cpp
|
||||
llcylinder.cpp
|
||||
lldebugmessagebox.cpp
|
||||
@@ -650,7 +650,6 @@ set(viewer_HEADER_FILES
|
||||
llconfirmationmanager.h
|
||||
llconsole.h
|
||||
llcontainerview.h
|
||||
llcurlrequest.h
|
||||
llcurrencyuimanager.h
|
||||
llcylinder.h
|
||||
lldebugmessagebox.h
|
||||
@@ -1541,7 +1540,7 @@ target_link_libraries(${VIEWER_BINARY_NAME}
|
||||
${LLINVENTORY_LIBRARIES}
|
||||
${LLMESSAGE_LIBRARIES}
|
||||
${LLPLUGIN_LIBRARIES}
|
||||
${AISTATEMACHINE_LIBRARIES}
|
||||
${STATEMACHINE_LIBRARIES}
|
||||
${LLPRIMITIVE_LIBRARIES}
|
||||
${LLRENDER_LIBRARIES}
|
||||
${FREETYPE_LIBRARIES}
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
/**
|
||||
* @file llcurlrequest.cpp
|
||||
* @brief Implementation of Request.
|
||||
*
|
||||
* Copyright (c) 2012, Aleric Inglewood.
|
||||
* Copyright (C) 2010, Linden Research, Inc.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 17/03/2012
|
||||
* Initial version, written by Aleric Inglewood @ SL
|
||||
*
|
||||
* 20/03/2012
|
||||
* Added copyright notice for Linden Lab for those parts that were
|
||||
* copied or derived from llcurl.cpp. The code of those parts are
|
||||
* already in their own llcurl.cpp, so they do not ever need to
|
||||
* even look at this file; the reason I added the copyright notice
|
||||
* is to make clear that I am not the author of 100% of this code
|
||||
* and hence I cannot change the license of it.
|
||||
*/
|
||||
|
||||
#include "linden_common.h"
|
||||
|
||||
#include "llsdserialize.h"
|
||||
#include "llcurlrequest.h"
|
||||
#include "statemachine/aicurleasyrequeststatemachine.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// class Request
|
||||
//
|
||||
|
||||
namespace AICurlInterface {
|
||||
|
||||
bool Request::get(std::string const& url, ResponderPtr responder)
|
||||
{
|
||||
return getByteRange(url, headers_t(), 0, -1, responder);
|
||||
}
|
||||
|
||||
bool Request::getByteRange(std::string const& url, headers_t const& headers, S32 offset, S32 length, ResponderPtr responder)
|
||||
{
|
||||
DoutEntering(dc::curl, "Request::getByteRange(" << url << ", ...)");
|
||||
|
||||
// This might throw AICurlNoEasyHandle.
|
||||
AICurlEasyRequestStateMachine* buffered_easy_request = new AICurlEasyRequestStateMachine(true);
|
||||
|
||||
{
|
||||
AICurlEasyRequest_wat buffered_easy_request_w(*buffered_easy_request->mCurlEasyRequest);
|
||||
|
||||
AICurlResponderBuffer_wat(*buffered_easy_request->mCurlEasyRequest)->prepRequest(buffered_easy_request_w, headers, responder);
|
||||
|
||||
buffered_easy_request_w->setopt(CURLOPT_HTTPGET, 1);
|
||||
if (length > 0)
|
||||
{
|
||||
std::string range = llformat("Range: bytes=%d-%d", offset, offset + length - 1);
|
||||
buffered_easy_request_w->addHeader(range.c_str());
|
||||
}
|
||||
|
||||
buffered_easy_request_w->finalizeRequest(url);
|
||||
}
|
||||
|
||||
buffered_easy_request->run();
|
||||
|
||||
return true; // We throw in case of problems.
|
||||
}
|
||||
|
||||
bool Request::post(std::string const& url, headers_t const& headers, std::string const& data, ResponderPtr responder, S32 time_out)
|
||||
{
|
||||
DoutEntering(dc::curl, "Request::post(" << url << ", ...)");
|
||||
|
||||
// This might throw AICurlNoEasyHandle.
|
||||
AICurlEasyRequestStateMachine* buffered_easy_request = new AICurlEasyRequestStateMachine(true);
|
||||
|
||||
{
|
||||
AICurlEasyRequest_wat buffered_easy_request_w(*buffered_easy_request->mCurlEasyRequest);
|
||||
AICurlResponderBuffer_wat buffer_w(*buffered_easy_request->mCurlEasyRequest);
|
||||
|
||||
buffer_w->prepRequest(buffered_easy_request_w, headers, responder);
|
||||
|
||||
buffer_w->getInput().write(data.data(), data.size());
|
||||
S32 bytes = buffer_w->getInput().str().length();
|
||||
buffered_easy_request_w->setPost(NULL, bytes);
|
||||
buffered_easy_request_w->addHeader("Content-Type: application/octet-stream");
|
||||
buffered_easy_request_w->finalizeRequest(url);
|
||||
|
||||
lldebugs << "POSTING: " << bytes << " bytes." << llendl;
|
||||
}
|
||||
|
||||
buffered_easy_request->run();
|
||||
|
||||
return true; // We throw in case of problems.
|
||||
}
|
||||
|
||||
bool Request::post(std::string const& url, headers_t const& headers, LLSD const& data, ResponderPtr responder, S32 time_out)
|
||||
{
|
||||
DoutEntering(dc::curl, "Request::post(" << url << ", ...)");
|
||||
|
||||
// This might throw AICurlNoEasyHandle.
|
||||
AICurlEasyRequestStateMachine* buffered_easy_request = new AICurlEasyRequestStateMachine(true);
|
||||
|
||||
{
|
||||
AICurlEasyRequest_wat buffered_easy_request_w(*buffered_easy_request->mCurlEasyRequest);
|
||||
AICurlResponderBuffer_wat buffer_w(*buffered_easy_request->mCurlEasyRequest);
|
||||
|
||||
buffer_w->prepRequest(buffered_easy_request_w, headers, responder);
|
||||
|
||||
LLSDSerialize::toXML(data, buffer_w->getInput());
|
||||
S32 bytes = buffer_w->getInput().str().length();
|
||||
buffered_easy_request_w->setPost(NULL, bytes);
|
||||
buffered_easy_request_w->addHeader("Content-Type: application/llsd+xml");
|
||||
buffered_easy_request_w->finalizeRequest(url);
|
||||
|
||||
lldebugs << "POSTING: " << bytes << " bytes." << llendl;
|
||||
}
|
||||
|
||||
buffered_easy_request->run();
|
||||
|
||||
return true; // We throw in case of problems.
|
||||
}
|
||||
|
||||
} // namespace AICurlInterface
|
||||
//==================================================================================
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
/**
|
||||
* @file llcurlrequest.h
|
||||
* @brief Declaration of class Request
|
||||
*
|
||||
* 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 <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.
|
||||
*
|
||||
* 17/03/2012
|
||||
* Initial version, written by Aleric Inglewood @ SL
|
||||
*/
|
||||
|
||||
#ifndef AICURLREQUEST_H
|
||||
#define AICURLREQUEST_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <boost/intrusive_ptr.hpp>
|
||||
|
||||
// Things defined in this namespace are called from elsewhere in the viewer code.
|
||||
namespace AICurlInterface {
|
||||
|
||||
// Forward declaration.
|
||||
class Responder;
|
||||
typedef boost::intrusive_ptr<Responder> ResponderPtr;
|
||||
|
||||
class Request {
|
||||
public:
|
||||
typedef std::vector<std::string> headers_t;
|
||||
|
||||
bool get(std::string const& url, ResponderPtr responder);
|
||||
bool getByteRange(std::string const& url, headers_t const& headers, S32 offset, S32 length, ResponderPtr responder);
|
||||
bool post(std::string const& url, headers_t const& headers, std::string const& data, ResponderPtr responder, S32 time_out = 0);
|
||||
bool post(std::string const& url, headers_t const& headers, LLSD const& data, ResponderPtr responder, S32 time_out = 0);
|
||||
};
|
||||
|
||||
} // namespace AICurlInterface
|
||||
|
||||
#endif
|
||||
@@ -78,7 +78,7 @@
|
||||
#include "llnetmap.h"
|
||||
#include "llrender.h"
|
||||
#include "llfloaterchat.h"
|
||||
#include "statemachine/aistatemachine.h"
|
||||
#include "aistatemachine.h"
|
||||
#include "aithreadsafe.h"
|
||||
#include "lldrawpoolbump.h"
|
||||
#include "emeraldboobutils.h"
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
#include "llappviewer.h"
|
||||
|
||||
#include "hippogridmanager.h"
|
||||
#include "statemachine/aicurleasyrequeststatemachine.h"
|
||||
#include "aicurleasyrequeststatemachine.h"
|
||||
|
||||
#ifdef CWDEBUG
|
||||
#include <libcwd/buf2str.h>
|
||||
|
||||
@@ -37,23 +37,17 @@ include_directories(
|
||||
)
|
||||
|
||||
set(statemachine_SOURCE_FILES
|
||||
aistatemachine.cpp
|
||||
aicurleasyrequeststatemachine.cpp
|
||||
aifilepicker.cpp
|
||||
aifetchinventoryfolder.cpp
|
||||
aievent.cpp
|
||||
aitimer.cpp
|
||||
)
|
||||
|
||||
set(statemachine_HEADER_FILES
|
||||
CMakeLists.txt
|
||||
aistatemachine.h
|
||||
aicurleasyrequeststatemachine.h
|
||||
aifilepicker.h
|
||||
aidirpicker.h
|
||||
aifetchinventoryfolder.h
|
||||
aievent.h
|
||||
aitimer.h
|
||||
)
|
||||
|
||||
set_source_files_properties(${statemachine_HEADER_FILES}
|
||||
|
||||
@@ -1,249 +0,0 @@
|
||||
/**
|
||||
* @file aicurleasyrequeststatemachine.cpp
|
||||
* @brief Implementation of AICurlEasyRequestStateMachine
|
||||
*
|
||||
* 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 <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.
|
||||
*
|
||||
* 06/05/2012
|
||||
* Initial version, written by Aleric Inglewood @ SL
|
||||
*/
|
||||
|
||||
#include "linden_common.h"
|
||||
#include "aicurleasyrequeststatemachine.h"
|
||||
#include "llcontrol.h"
|
||||
|
||||
enum curleasyrequeststatemachine_state_type {
|
||||
AICurlEasyRequestStateMachine_addRequest = AIStateMachine::max_state,
|
||||
AICurlEasyRequestStateMachine_waitAdded,
|
||||
AICurlEasyRequestStateMachine_added,
|
||||
AICurlEasyRequestStateMachine_timedOut, // This must be smaller than the rest, so they always overrule.
|
||||
AICurlEasyRequestStateMachine_finished,
|
||||
AICurlEasyRequestStateMachine_removed, // The removed states must be largest two, so they are never ignored.
|
||||
AICurlEasyRequestStateMachine_removed_after_finished
|
||||
};
|
||||
|
||||
char const* AICurlEasyRequestStateMachine::state_str_impl(state_type run_state) const
|
||||
{
|
||||
switch(run_state)
|
||||
{
|
||||
AI_CASE_RETURN(AICurlEasyRequestStateMachine_addRequest);
|
||||
AI_CASE_RETURN(AICurlEasyRequestStateMachine_waitAdded);
|
||||
AI_CASE_RETURN(AICurlEasyRequestStateMachine_added);
|
||||
AI_CASE_RETURN(AICurlEasyRequestStateMachine_timedOut);
|
||||
AI_CASE_RETURN(AICurlEasyRequestStateMachine_finished);
|
||||
AI_CASE_RETURN(AICurlEasyRequestStateMachine_removed);
|
||||
AI_CASE_RETURN(AICurlEasyRequestStateMachine_removed_after_finished);
|
||||
}
|
||||
return "UNKNOWN STATE";
|
||||
}
|
||||
|
||||
void AICurlEasyRequestStateMachine::initialize_impl(void)
|
||||
{
|
||||
{
|
||||
AICurlEasyRequest_wat curlEasyRequest_w(*mCurlEasyRequest);
|
||||
llassert(curlEasyRequest_w->is_finalized()); // Call finalizeRequest(url) before calling run().
|
||||
curlEasyRequest_w->send_events_to(this);
|
||||
}
|
||||
mAdded = false;
|
||||
mTimedOut = false;
|
||||
mFinished = false;
|
||||
mHandled = false;
|
||||
set_state(AICurlEasyRequestStateMachine_addRequest);
|
||||
}
|
||||
|
||||
// CURL-THREAD
|
||||
void AICurlEasyRequestStateMachine::added_to_multi_handle(AICurlEasyRequest_wat&)
|
||||
{
|
||||
set_state(AICurlEasyRequestStateMachine_added);
|
||||
}
|
||||
|
||||
// CURL-THREAD
|
||||
void AICurlEasyRequestStateMachine::finished(AICurlEasyRequest_wat&)
|
||||
{
|
||||
mFinished = true;
|
||||
set_state(AICurlEasyRequestStateMachine_finished);
|
||||
}
|
||||
|
||||
// CURL-THREAD
|
||||
void AICurlEasyRequestStateMachine::removed_from_multi_handle(AICurlEasyRequest_wat&)
|
||||
{
|
||||
set_state(mFinished ? AICurlEasyRequestStateMachine_removed_after_finished : AICurlEasyRequestStateMachine_removed);
|
||||
}
|
||||
|
||||
void AICurlEasyRequestStateMachine::multiplex_impl(void)
|
||||
{
|
||||
mSetStateLock.lock();
|
||||
state_type current_state = mRunState;
|
||||
mSetStateLock.unlock();
|
||||
switch (current_state)
|
||||
{
|
||||
case AICurlEasyRequestStateMachine_addRequest:
|
||||
{
|
||||
set_state(AICurlEasyRequestStateMachine_waitAdded);
|
||||
idle(AICurlEasyRequestStateMachine_waitAdded); // Wait till AICurlEasyRequestStateMachine::added_to_multi_handle() is called.
|
||||
// Only AFTER going idle, add request to curl thread; this is needed because calls to set_state() are
|
||||
// ignored when the statemachine is not idle, and theoretically the callbacks could be called
|
||||
// immediately after this call.
|
||||
mAdded = true;
|
||||
mCurlEasyRequest.addRequest(); // This causes the state to be changed, now or later, to
|
||||
// AICurlEasyRequestStateMachine_added, then
|
||||
// AICurlEasyRequestStateMachine_finished and then
|
||||
// AICurlEasyRequestStateMachine_removed_after_finished.
|
||||
|
||||
// The first two states might be skipped thus, and the state at this point is one of
|
||||
// 1) AICurlEasyRequestStateMachine_waitAdded (idle)
|
||||
// 2) AICurlEasyRequestStateMachine_added (running)
|
||||
// 3) AICurlEasyRequestStateMachine_finished (running)
|
||||
// 4) AICurlEasyRequestStateMachine_removed_after_finished (running)
|
||||
|
||||
// Set an inactivity timer.
|
||||
// This shouldn't really be necessary, except in the case of a bug
|
||||
// in libcurl; but lets be sure and set a timer for inactivity.
|
||||
static LLCachedControl<F32> CurlRequestTimeOut("CurlRequestTimeOut", 40.f);
|
||||
mTimer = new AIPersistentTimer; // Do not delete timer upon expiration.
|
||||
mTimer->setInterval(CurlRequestTimeOut);
|
||||
mTimer->run(this, AICurlEasyRequestStateMachine_timedOut, false, false);
|
||||
break;
|
||||
}
|
||||
case AICurlEasyRequestStateMachine_added:
|
||||
{
|
||||
// The request was added to the multi handle. This is a no-op, which is good cause
|
||||
// this state might be skipped anyway ;).
|
||||
idle(current_state); // Wait for the next event.
|
||||
|
||||
// The state at this point is one of
|
||||
// 1) AICurlEasyRequestStateMachine_added (idle)
|
||||
// 2) AICurlEasyRequestStateMachine_finished (running)
|
||||
// 3) AICurlEasyRequestStateMachine_removed_after_finished (running)
|
||||
break;
|
||||
}
|
||||
case AICurlEasyRequestStateMachine_timedOut:
|
||||
{
|
||||
// It is possible that exactly at this point the state changes into
|
||||
// AICurlEasyRequestStateMachine_finished, with as result that mTimedOut
|
||||
// is set while we will continue with that state. Hence that mTimedOut
|
||||
// is explicitly reset in that state.
|
||||
|
||||
// Libcurl failed to deliver within a reasonable time... Abort operation in order
|
||||
// to free this curl easy handle and to notify the application that it didn't work.
|
||||
mTimedOut = true;
|
||||
llassert(mAdded);
|
||||
mAdded = false;
|
||||
mCurlEasyRequest.removeRequest();
|
||||
idle(current_state); // Wait till AICurlEasyRequestStateMachine::removed_from_multi_handle() is called.
|
||||
break;
|
||||
}
|
||||
case AICurlEasyRequestStateMachine_finished:
|
||||
case AICurlEasyRequestStateMachine_removed_after_finished:
|
||||
{
|
||||
if (!mHandled)
|
||||
{
|
||||
// Only do this once.
|
||||
mHandled = true;
|
||||
|
||||
// Stop the timer. Note that it's the main thread that generates timer events,
|
||||
// so we're certain that there will be no time out anymore if we reach this point.
|
||||
mTimer->abort();
|
||||
|
||||
// The request finished and either data or an error code is available.
|
||||
if (mBuffered)
|
||||
{
|
||||
AICurlEasyRequest_wat easy_request_w(*mCurlEasyRequest);
|
||||
AICurlResponderBuffer_wat buffered_easy_request_w(*mCurlEasyRequest);
|
||||
buffered_easy_request_w->processOutput(easy_request_w);
|
||||
}
|
||||
}
|
||||
|
||||
if (current_state == AICurlEasyRequestStateMachine_finished)
|
||||
{
|
||||
idle(current_state); // Wait till AICurlEasyRequestStateMachine::removed_from_multi_handle() is called.
|
||||
break;
|
||||
}
|
||||
|
||||
// See above.
|
||||
mTimedOut = false;
|
||||
/* Fall-Through */
|
||||
}
|
||||
case AICurlEasyRequestStateMachine_removed:
|
||||
{
|
||||
// The request was removed from the multi handle.
|
||||
if (mBuffered && mTimedOut)
|
||||
{
|
||||
AICurlEasyRequest_wat easy_request_w(*mCurlEasyRequest);
|
||||
AICurlResponderBuffer_wat buffered_easy_request_w(*mCurlEasyRequest);
|
||||
buffered_easy_request_w->timed_out();
|
||||
}
|
||||
|
||||
// We're done. If we timed out, abort -- or else the application will
|
||||
// think that getResult() will return a valid error code from libcurl.
|
||||
if (mTimedOut)
|
||||
abort();
|
||||
else
|
||||
finish();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AICurlEasyRequestStateMachine::abort_impl(void)
|
||||
{
|
||||
DoutEntering(dc::curl, "AICurlEasyRequestStateMachine::abort_impl() [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]");
|
||||
// Revert call to addRequest() if that was already called (and the request wasn't removed again already).
|
||||
if (mAdded)
|
||||
{
|
||||
// Note that it's safe to call this even if the curl thread already removed it, or will removes it
|
||||
// after we called this, before processing the remove command; only the curl thread calls
|
||||
// MultiHandle::remove_easy_request, which is a no-op when called twice for the same easy request.
|
||||
mAdded = false;
|
||||
mCurlEasyRequest.removeRequest();
|
||||
}
|
||||
}
|
||||
|
||||
void AICurlEasyRequestStateMachine::finish_impl(void)
|
||||
{
|
||||
DoutEntering(dc::curl, "AICurlEasyRequestStateMachine::finish_impl() [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]");
|
||||
// Revoke callbacks.
|
||||
{
|
||||
AICurlEasyRequest_wat curl_easy_request_w(*mCurlEasyRequest);
|
||||
curl_easy_request_w->send_events_to(NULL);
|
||||
curl_easy_request_w->revokeCallbacks();
|
||||
}
|
||||
// Note that even if the timer expired, it wasn't deleted because we used AIPersistentTimer; so mTimer is still valid.
|
||||
// Stop the timer, if it's still running.
|
||||
if (!mHandled)
|
||||
mTimer->abort();
|
||||
// Auto clean up ourselves.
|
||||
kill();
|
||||
}
|
||||
|
||||
AICurlEasyRequestStateMachine::AICurlEasyRequestStateMachine(bool buffered) : mBuffered(buffered), mCurlEasyRequest(buffered)
|
||||
{
|
||||
Dout(dc::statemachine, "Calling AICurlEasyRequestStateMachine(" << (buffered ? "true" : "false") << ") [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]");
|
||||
}
|
||||
|
||||
AICurlEasyRequestStateMachine::~AICurlEasyRequestStateMachine()
|
||||
{
|
||||
Dout(dc::statemachine, "Calling ~AICurlEasyRequestStateMachine() [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]");
|
||||
}
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
/**
|
||||
* @file aicurleasyrequest.h
|
||||
* @brief Perform a curl easy request.
|
||||
*
|
||||
* 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 <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.
|
||||
*
|
||||
* 06/05/2012
|
||||
* Initial version, written by Aleric Inglewood @ SL
|
||||
*/
|
||||
|
||||
#ifndef AICURLEASYREQUEST_H
|
||||
#define AICURLEASYREQUEST_H
|
||||
|
||||
#include "aistatemachine.h"
|
||||
#include "aitimer.h"
|
||||
#include "aicurl.h"
|
||||
|
||||
// A curl easy request state machine.
|
||||
//
|
||||
// Before calling cersm.run() initialize the object (cersm) as follows:
|
||||
//
|
||||
// AICurlEasyRequest_wat cersm_w(cersm);
|
||||
// cersm_w->setopt(...); // etc, see the interface of AICurlPrivate::CurlEasyRequest and it's base class AICurlPrivate::CurlEasyHandle.
|
||||
//
|
||||
// When the state machine finishes, call aborted() to check
|
||||
// whether or not the statemachine succeeded in fetching
|
||||
// the URL or not.
|
||||
//
|
||||
// Objects of this type can be reused multiple times, see
|
||||
// also the documentation of AIStateMachine.
|
||||
//
|
||||
// Construction of a AICurlEasyRequestStateMachine might throw AICurlNoEasyHandle.
|
||||
class AICurlEasyRequestStateMachine : public AIStateMachine, public AICurlEasyHandleEvents {
|
||||
public:
|
||||
AICurlEasyRequestStateMachine(bool buffered);
|
||||
|
||||
// Transparent access.
|
||||
AICurlEasyRequest mCurlEasyRequest;
|
||||
|
||||
private:
|
||||
bool mBuffered; // Argument used for construction of mCurlEasyRequest.
|
||||
bool mAdded; // Set when the last command to the curl thread was to add the request.
|
||||
bool mTimedOut; // Set if the expiration timer timed out.
|
||||
bool mFinished; // Set by the curl thread to signal it finished.
|
||||
bool mHandled; // Set when we processed the received data.
|
||||
AITimer* mTimer; // Expiration timer.
|
||||
|
||||
protected:
|
||||
// AICurlEasyRequest Events.
|
||||
|
||||
// Called when this curl easy handle was added to a multi handle.
|
||||
/*virtual*/ void added_to_multi_handle(AICurlEasyRequest_wat&);
|
||||
|
||||
// Called when this curl easy handle finished processing (right before it is removed from the multi handle).
|
||||
/*virtual*/ void finished(AICurlEasyRequest_wat&);
|
||||
|
||||
// Called after this curl easy handle was removed from a multi handle.
|
||||
/*virtual*/ void removed_from_multi_handle(AICurlEasyRequest_wat&);
|
||||
|
||||
protected:
|
||||
// AIStateMachine implementations.
|
||||
|
||||
// Call finish() (or abort()), not delete.
|
||||
/*virtual*/ ~AICurlEasyRequestStateMachine();
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,638 +0,0 @@
|
||||
/**
|
||||
* @file aistatemachine.cpp
|
||||
* @brief Implementation of AIStateMachine
|
||||
*
|
||||
* Copyright (c) 2010, 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.
|
||||
*
|
||||
* 01/03/2010
|
||||
* Initial version, written by Aleric Inglewood @ SL
|
||||
*/
|
||||
|
||||
#include "linden_common.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "llcallbacklist.h"
|
||||
#include "llcontrol.h"
|
||||
#include "llfasttimer.h"
|
||||
#include "aithreadsafe.h"
|
||||
#include "aistatemachine.h"
|
||||
|
||||
extern F64 calc_clock_frequency(void);
|
||||
|
||||
extern LLControlGroup gSavedSettings;
|
||||
|
||||
// Local variables.
|
||||
namespace {
|
||||
struct QueueElementComp;
|
||||
|
||||
class QueueElement {
|
||||
private:
|
||||
AIStateMachine* mStateMachine;
|
||||
U64 mRuntime;
|
||||
|
||||
public:
|
||||
QueueElement(AIStateMachine* statemachine) : mStateMachine(statemachine), mRuntime(0) { }
|
||||
friend bool operator==(QueueElement const& e1, QueueElement const& e2) { return e1.mStateMachine == e2.mStateMachine; }
|
||||
friend struct QueueElementComp;
|
||||
|
||||
AIStateMachine& statemachine(void) const { return *mStateMachine; }
|
||||
void add(U64 count) { mRuntime += count; }
|
||||
};
|
||||
|
||||
struct QueueElementComp {
|
||||
bool operator()(QueueElement const& e1, QueueElement const& e2) const { return e1.mRuntime < e2.mRuntime; }
|
||||
};
|
||||
|
||||
typedef std::vector<QueueElement> active_statemachines_type;
|
||||
active_statemachines_type active_statemachines;
|
||||
typedef std::vector<AIStateMachine*> continued_statemachines_type;
|
||||
struct cscm_type
|
||||
{
|
||||
continued_statemachines_type continued_statemachines;
|
||||
bool calling_mainloop;
|
||||
};
|
||||
AIThreadSafeDC<cscm_type> continued_statemachines_and_calling_mainloop;
|
||||
}
|
||||
|
||||
// static
|
||||
AIThreadSafeSimpleDC<U64> AIStateMachine::sMaxCount;
|
||||
|
||||
void AIStateMachine::updateSettings(void)
|
||||
{
|
||||
static const LLCachedControl<U32> StateMachineMaxTime("StateMachineMaxTime", 20);
|
||||
static U32 last_StateMachineMaxTime = 0;
|
||||
if (last_StateMachineMaxTime != StateMachineMaxTime)
|
||||
{
|
||||
Dout(dc::statemachine, "Initializing AIStateMachine::sMaxCount");
|
||||
*AIAccess<U64>(sMaxCount) = calc_clock_frequency() * StateMachineMaxTime / 1000;
|
||||
last_StateMachineMaxTime = StateMachineMaxTime;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
|
||||
void AIStateMachine::run(AIStateMachine* parent, state_type new_parent_state, bool abort_parent, bool on_abort_signal_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, 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 || mState == bs_callback);
|
||||
|
||||
// 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;
|
||||
mOnAbortSignalParent = on_abort_signal_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;
|
||||
|
||||
// Set mIdle to false and add statemachine to continued_statemachines.
|
||||
mSetStateLock.lock();
|
||||
locked_cont();
|
||||
}
|
||||
|
||||
void AIStateMachine::run(callback_type::signal_type::slot_type const& slot)
|
||||
{
|
||||
DoutEntering(dc::statemachine, "AIStateMachine::run(<slot>) [" << (void*)this << "]");
|
||||
// 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 || 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;
|
||||
|
||||
// Set mIdle to false and add statemachine to continued_statemachines.
|
||||
mSetStateLock.lock();
|
||||
locked_cont();
|
||||
}
|
||||
|
||||
void AIStateMachine::idle(void)
|
||||
{
|
||||
DoutEntering(dc::statemachine, "AIStateMachine::idle() [" << (void*)this << "]");
|
||||
llassert(is_main_thread());
|
||||
llassert(!mIdle);
|
||||
mIdle = true;
|
||||
mSleep = 0;
|
||||
#ifdef SHOW_ASSERT
|
||||
mCalledThreadUnsafeIdle = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void AIStateMachine::idle(state_type current_run_state)
|
||||
{
|
||||
DoutEntering(dc::statemachine, "AIStateMachine::idle(" << state_str(current_run_state) << ") [" << (void*)this << "]");
|
||||
llassert(is_main_thread());
|
||||
llassert(!mIdle);
|
||||
mSetStateLock.lock();
|
||||
// Only go idle if the run state is (still) what we expect it to be.
|
||||
// Otherwise assume that another thread called set_state() and continue running.
|
||||
if (current_run_state == mRunState)
|
||||
{
|
||||
mIdle = true;
|
||||
mSleep = 0;
|
||||
}
|
||||
mSetStateLock.unlock();
|
||||
}
|
||||
|
||||
// About thread safeness:
|
||||
//
|
||||
// The main thread initializes a statemachine and calls run, so a statemachine
|
||||
// runs in the main thread. However, it is allowed that a state calls idle()
|
||||
// and then allows one or more other threads to call cont() upon some
|
||||
// event (only once, of course, as idle() has to be called before cont()
|
||||
// can be called again-- and a non-main thread is not allowed to call idle()).
|
||||
// Instead of cont() one may also call set_state().
|
||||
// Of course, this may give rise to a race condition; if that happens then
|
||||
// the thread that calls cont() (set_state()) first is serviced, and the other
|
||||
// thread(s) are ignored, as if they never called cont().
|
||||
void AIStateMachine::locked_cont(void)
|
||||
{
|
||||
DoutEntering(dc::statemachine, "AIStateMachine::locked_cont() [" << (void*)this << "]");
|
||||
llassert(mIdle);
|
||||
// Atomic test mActive and change mIdle.
|
||||
mIdleActive.lock();
|
||||
#ifdef SHOW_ASSERT
|
||||
mContThread = apr_os_thread_current();
|
||||
#endif
|
||||
mIdle = false;
|
||||
bool not_active = mActive == as_idle;
|
||||
mIdleActive.unlock();
|
||||
// mActive is only changed in AIStateMachine::mainloop, by the main-thread, and
|
||||
// here, possibly by any thread. However, after setting mIdle to false above, it
|
||||
// is impossible for any thread to come here, until after the main-thread called
|
||||
// idle(). So, if this is the main thread then that certainly isn't going to
|
||||
// happen until we left this function, while if this is another thread and the
|
||||
// state machine is already running in the main thread then not_active is false
|
||||
// and we're already at the end of this function.
|
||||
// If not_active is true then main-thread is not running this statemachine.
|
||||
// It might call cont() (or set_state()) but never locked_cont(), and will never
|
||||
// start actually running until we are done here and release the lock on
|
||||
// continued_statemachines_and_calling_mainloop again. It is therefore safe
|
||||
// to release mSetStateLock here, with as advantage that if we're not the main-
|
||||
// thread and not_active is true, then the main-thread won't block when it has
|
||||
// a timer running that times out and calls set_state().
|
||||
mSetStateLock.unlock();
|
||||
if (not_active)
|
||||
{
|
||||
AIWriteAccess<cscm_type> cscm_w(continued_statemachines_and_calling_mainloop);
|
||||
// See above: it is not possible that mActive was changed since not_active
|
||||
// was set to true above.
|
||||
llassert_always(mActive == as_idle);
|
||||
Dout(dc::statemachine, "Adding " << (void*)this << " to continued_statemachines");
|
||||
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;
|
||||
llassert_always(!mIdle); // It should never happen that the main thread calls idle(), while another thread calls cont() concurrently.
|
||||
}
|
||||
}
|
||||
|
||||
void AIStateMachine::set_state(state_type state)
|
||||
{
|
||||
DoutEntering(dc::statemachine, "AIStateMachine::set_state(" << state_str(state) << ") [" << (void*)this << "]");
|
||||
|
||||
// Stop race condition of multiple threads calling cont() or set_state() here.
|
||||
mSetStateLock.lock();
|
||||
|
||||
// Do not call set_state() unless running.
|
||||
llassert(mState == bs_run || !LLThread::is_main_thread());
|
||||
|
||||
// If this function is called from another thread than the main thread, then we have to ignore
|
||||
// it if we're not idle and the state is less than the current state. The main thread must
|
||||
// be able to change the state to anything (also smaller values). Note that that only can work
|
||||
// if the main thread itself at all times cancels thread callbacks that call set_state()
|
||||
// before calling idle() again!
|
||||
//
|
||||
// Thus: main thead calls idle(), and tells one or more threads to do callbacks on events,
|
||||
// which (might) call set_state(). If the main thread calls set_state first (currently only
|
||||
// possible as a result of the use of a timer) it will set mIdle to false (here) then cancel
|
||||
// the call backs from the other threads and only then call idle() again.
|
||||
// Thus if you want other threads get here while mIdle is false to be ignored then the
|
||||
// main thread should use a large value for the new run state.
|
||||
//
|
||||
// If a non-main thread calls set_state first, then the state is changed but the main thread
|
||||
// can still override it if it calls set_state before handling the new state; in the latter
|
||||
// case it would still be as if the call from the non-main thread was ignored.
|
||||
//
|
||||
// Concurrent calls from non-main threads however, always result in the largest state
|
||||
// to prevail.
|
||||
|
||||
// If the state machine is already running, and we are not the main-thread and the new
|
||||
// state is less than the current state, ignore it.
|
||||
// Also, if abort() or finish() was called, then we should just ignore it.
|
||||
if (mState != bs_run ||
|
||||
(!mIdle && state <= mRunState && !LLThread::is_main_thread()))
|
||||
{
|
||||
#ifdef SHOW_ASSERT
|
||||
// It's a bit weird if the same thread does two calls on a row where the second call
|
||||
// has a smaller value: warn about that.
|
||||
if (mState == bs_run && mContThread == apr_os_thread_current())
|
||||
{
|
||||
llwarns << "Ignoring call to set_state(" << state_str(state) <<
|
||||
") by non-main thread before main-thread could react on previous call, "
|
||||
"because new state is smaller than old state (" << state_str(mRunState) << ")." << llendl;
|
||||
}
|
||||
#endif
|
||||
mSetStateLock.unlock();
|
||||
return; // Ignore.
|
||||
}
|
||||
|
||||
// Do not call idle() when set_state is called from another thread; use idle(state_type) instead.
|
||||
llassert(!mCalledThreadUnsafeIdle || LLThread::is_main_thread());
|
||||
|
||||
// Change mRunState to the requested value.
|
||||
if (mRunState != state)
|
||||
{
|
||||
mRunState = state;
|
||||
Dout(dc::statemachine, "mRunState set to " << state_str(mRunState));
|
||||
}
|
||||
|
||||
// Continue the state machine if appropriate.
|
||||
if (mIdle)
|
||||
locked_cont(); // This unlocks mSetStateLock.
|
||||
else
|
||||
mSetStateLock.unlock();
|
||||
|
||||
// If we get here then mIdle is false, so only mRunState can still be changed but we won't
|
||||
// call locked_cont() anymore. When the main thread finally picks up on the state change,
|
||||
// it will cancel any possible callbacks from other threads and process the largest state
|
||||
// that this function was called with in the meantime.
|
||||
}
|
||||
|
||||
void AIStateMachine::abort(void)
|
||||
{
|
||||
DoutEntering(dc::statemachine, "AIStateMachine::abort() [" << (void*)this << "]");
|
||||
// It's possible that abort() is called before calling AIStateMachine::multiplex.
|
||||
// In that case the statemachine wasn't initialized yet and we should just kill() it.
|
||||
if (LL_UNLIKELY(mState == bs_initialize))
|
||||
{
|
||||
// It's ok to use the thread-unsafe idle() here, because if the statemachine
|
||||
// wasn't started yet, then other threads won't call set_state() on it.
|
||||
if (!mIdle)
|
||||
idle();
|
||||
// run() calls locked_cont() after which the top of the mainloop adds this
|
||||
// state machine to active_statemachines. Therefore, if the following fails
|
||||
// then either the same statemachine called run() immediately followed by abort(),
|
||||
// which is not allowed; or there were two active statemachines running,
|
||||
// the first created a new statemachine and called run() on it, and then
|
||||
// the other (before reaching the top of the mainloop) called abort() on
|
||||
// that freshly created statemachine. Obviously, this is highly unlikely,
|
||||
// but if that is the case then here we bump the statemachine into
|
||||
// continued_statemachines to prevent kill() to delete this statemachine:
|
||||
// the caller of abort() does not expect that.
|
||||
if (LL_UNLIKELY(mActive == as_idle))
|
||||
{
|
||||
mSetStateLock.lock();
|
||||
locked_cont();
|
||||
idle();
|
||||
}
|
||||
kill();
|
||||
}
|
||||
else
|
||||
{
|
||||
llassert(mState == bs_run);
|
||||
mSetStateLock.lock();
|
||||
mState = bs_abort; // Causes additional calls to set_state to be ignored.
|
||||
mSetStateLock.unlock();
|
||||
abort_impl();
|
||||
mAborted = true;
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
void AIStateMachine::finish(void)
|
||||
{
|
||||
DoutEntering(dc::statemachine, "AIStateMachine::finish() [" << (void*)this << "]");
|
||||
mSetStateLock.lock();
|
||||
llassert(mState == bs_run || mState == bs_abort);
|
||||
// It is possible that mIdle is true when abort or finish was called from
|
||||
// outside multiplex_impl. However, that only may be done by the main thread.
|
||||
llassert(!mIdle || is_main_thread());
|
||||
if (!mIdle)
|
||||
idle(); // After calling this, we don't want other threads to call set_state() anymore.
|
||||
mState = bs_finish; // Causes additional calls to set_state to be ignored.
|
||||
mSetStateLock.unlock();
|
||||
finish_impl();
|
||||
// Did finish_impl call kill()? Then that is only the default. Remember it.
|
||||
bool default_delete = (mState == bs_killed);
|
||||
mState = bs_finish;
|
||||
if (mParent)
|
||||
{
|
||||
// It is possible that the parent is not running when the parent is in fact aborting and called
|
||||
// abort on this object from it's abort_impl function. It that case we don't want to recursively
|
||||
// call abort again (or change it's state).
|
||||
if (mParent->running())
|
||||
{
|
||||
if (mAborted && mAbortParent)
|
||||
{
|
||||
mParent->abort();
|
||||
mParent = NULL;
|
||||
}
|
||||
else if (!mAborted || mOnAbortSignalParent)
|
||||
{
|
||||
mParent->set_state(mNewParentState);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
if (mState == bs_killed && mActive == as_idle)
|
||||
{
|
||||
// Bump the statemachine onto the active statemachine list, or else it won't be deleted.
|
||||
mSetStateLock.lock();
|
||||
locked_cont();
|
||||
idle();
|
||||
}
|
||||
}
|
||||
|
||||
void AIStateMachine::kill(void)
|
||||
{
|
||||
DoutEntering(dc::statemachine, "AIStateMachine::kill() [" << (void*)this << "]");
|
||||
// Should only be called from finish() (or when not running (bs_initialize)).
|
||||
// However, also allow multiple calls to kill() on a row (bs_killed) (which effectively don't do anything).
|
||||
llassert(mIdle && (mState == bs_callback || mState == bs_finish || mState == bs_initialize || mState == bs_killed));
|
||||
base_state_type prev_state = mState;
|
||||
mState = bs_killed;
|
||||
if (prev_state == bs_initialize && mActive == as_idle)
|
||||
{
|
||||
// We're not running (ie being deleted by a parent statemachine), delete it immediately.
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
// Return stringified 'state'.
|
||||
char const* AIStateMachine::state_str(state_type state)
|
||||
{
|
||||
if (state >= min_state && state < max_state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
AI_CASE_RETURN(bs_initialize);
|
||||
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);
|
||||
}
|
||||
}
|
||||
return state_str_impl(state);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
|
||||
void AIStateMachine::multiplex(U64 current_time)
|
||||
{
|
||||
// Return immediately when this state machine is sleeping.
|
||||
// A negative value of mSleep means we're counting frames,
|
||||
// a positive value means we're waiting till a certain
|
||||
// amount of time has passed.
|
||||
if (mSleep != 0)
|
||||
{
|
||||
if (mSleep < 0)
|
||||
{
|
||||
if (++mSleep)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (current_time < (U64)mSleep)
|
||||
return;
|
||||
mSleep = 0;
|
||||
}
|
||||
}
|
||||
|
||||
DoutEntering(dc::statemachine, "AIStateMachine::multiplex() [" << (void*)this << "] [with state: " << state_str(mState == bs_run ? mRunState : mState) << "]");
|
||||
llassert(mState == bs_initialize || mState == bs_run);
|
||||
|
||||
// Real state machine starts here.
|
||||
if (mState == bs_initialize)
|
||||
{
|
||||
mAborted = false;
|
||||
mState = bs_run;
|
||||
initialize_impl();
|
||||
if (mAborted || mState != bs_run)
|
||||
return;
|
||||
}
|
||||
multiplex_impl();
|
||||
}
|
||||
|
||||
//static
|
||||
void AIStateMachine::add_continued_statemachines(void)
|
||||
{
|
||||
AIReadAccess<cscm_type> cscm_r(continued_statemachines_and_calling_mainloop);
|
||||
bool nonempty = false;
|
||||
for (continued_statemachines_type::const_iterator iter = cscm_r->continued_statemachines.begin(); iter != cscm_r->continued_statemachines.end(); ++iter)
|
||||
{
|
||||
nonempty = true;
|
||||
active_statemachines.push_back(QueueElement(*iter));
|
||||
Dout(dc::statemachine, "Adding " << (void*)*iter << " to active_statemachines");
|
||||
(*iter)->mActive = as_active;
|
||||
}
|
||||
if (nonempty)
|
||||
AIWriteAccess<cscm_type>(cscm_r)->continued_statemachines.clear();
|
||||
}
|
||||
|
||||
static LLFastTimer::DeclareTimer FTM_STATEMACHINE("State Machine");
|
||||
// static
|
||||
void AIStateMachine::mainloop(void*)
|
||||
{
|
||||
LLFastTimer t(FTM_STATEMACHINE);
|
||||
add_continued_statemachines();
|
||||
llassert(!active_statemachines.empty());
|
||||
// Run one or more state machines.
|
||||
U64 total_clocks = 0;
|
||||
U64 max_count = *AIAccess<U64>(sMaxCount);
|
||||
for (active_statemachines_type::iterator iter = active_statemachines.begin(); iter != active_statemachines.end(); ++iter)
|
||||
{
|
||||
AIStateMachine& statemachine(iter->statemachine());
|
||||
if (!statemachine.mIdle)
|
||||
{
|
||||
U64 start = LLFastTimer::getCPUClockCount64();
|
||||
// This might call idle() and then pass the statemachine to another thread who then may call cont().
|
||||
// Hence, after this isn't not sure what mIdle is, and it can change from true to false at any moment,
|
||||
// if it is true after this function returns.
|
||||
statemachine.multiplex(start);
|
||||
U64 delta = LLFastTimer::getCPUClockCount64() - start;
|
||||
iter->add(delta);
|
||||
total_clocks += delta;
|
||||
if (total_clocks >= max_count)
|
||||
{
|
||||
#ifndef LL_RELEASE_FOR_DOWNLOAD
|
||||
llwarns << "AIStateMachine::mainloop did run for " << (total_clocks * 1000 / calc_clock_frequency()) << " ms." << llendl;
|
||||
#endif
|
||||
std::sort(active_statemachines.begin(), active_statemachines.end(), QueueElementComp());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove idle state machines from the loop.
|
||||
active_statemachines_type::iterator iter = active_statemachines.begin();
|
||||
while (iter != active_statemachines.end())
|
||||
{
|
||||
AIStateMachine& statemachine(iter->statemachine());
|
||||
// Atomic test mIdle and change mActive.
|
||||
bool locked = statemachine.mIdleActive.tryLock();
|
||||
// If the lock failed, then another thread is in the middle of calling cont(),
|
||||
// thus mIdle will end up false. So, there is no reason to block here; just
|
||||
// treat mIdle as false already.
|
||||
if (locked && statemachine.mIdle)
|
||||
{
|
||||
// Without the lock, it would be possible that another thread called cont() right here,
|
||||
// changing mIdle to false again but NOT adding the statemachine to continued_statemachines,
|
||||
// thinking it is in active_statemachines (and it is), while immediately below it is
|
||||
// erased from active_statemachines.
|
||||
statemachine.mActive = as_idle;
|
||||
// Now, calling cont() is ok -- as that will cause the statemachine to be added to
|
||||
// continued_statemachines, so it's fine in that case-- even necessary-- to remove it from
|
||||
// active_statemachines regardless, and we can release the lock here.
|
||||
statemachine.mIdleActive.unlock();
|
||||
Dout(dc::statemachine, "Erasing " << (void*)&statemachine << " from active_statemachines");
|
||||
iter = active_statemachines.erase(iter);
|
||||
if (statemachine.mState == bs_killed)
|
||||
{
|
||||
Dout(dc::statemachine, "Deleting " << (void*)&statemachine);
|
||||
delete &statemachine;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (locked)
|
||||
{
|
||||
statemachine.mIdleActive.unlock();
|
||||
}
|
||||
llassert(statemachine.mActive == as_active); // It should not be possible that another thread called cont() and changed this when we are we are not idle.
|
||||
llassert(statemachine.mState == bs_run || statemachine.mState == bs_initialize);
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
if (active_statemachines.empty())
|
||||
{
|
||||
// If this was the last state machine, remove mainloop from the IdleCallbacks.
|
||||
AIReadAccess<cscm_type> cscm_r(continued_statemachines_and_calling_mainloop);
|
||||
if (cscm_r->continued_statemachines.empty() && cscm_r->calling_mainloop)
|
||||
{
|
||||
Dout(dc::statemachine, "Removing AIStateMachine::mainloop from gIdleCallbacks");
|
||||
AIWriteAccess<cscm_type>(cscm_r)->calling_mainloop = false;
|
||||
gIdleCallbacks.deleteFunction(&AIStateMachine::mainloop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void AIStateMachine::flush(void)
|
||||
{
|
||||
DoutEntering(dc::curl, "AIStateMachine::flush(void)");
|
||||
add_continued_statemachines();
|
||||
// Abort all state machines.
|
||||
for (active_statemachines_type::iterator iter = active_statemachines.begin(); iter != active_statemachines.end(); ++iter)
|
||||
{
|
||||
AIStateMachine& statemachine(iter->statemachine());
|
||||
if (statemachine.abortable())
|
||||
statemachine.abort();
|
||||
}
|
||||
for (int batch = 0;; ++batch)
|
||||
{
|
||||
// Run mainloop until all state machines are idle.
|
||||
for(;;)
|
||||
{
|
||||
{
|
||||
AIReadAccess<cscm_type> cscm_r(continued_statemachines_and_calling_mainloop);
|
||||
if (!cscm_r->calling_mainloop)
|
||||
break;
|
||||
}
|
||||
mainloop(NULL);
|
||||
}
|
||||
if (batch == 1)
|
||||
break;
|
||||
add_continued_statemachines();
|
||||
// Kill all state machines.
|
||||
for (active_statemachines_type::iterator iter = active_statemachines.begin(); iter != active_statemachines.end(); ++iter)
|
||||
{
|
||||
AIStateMachine& statemachine(iter->statemachine());
|
||||
if (statemachine.running())
|
||||
statemachine.kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,399 +0,0 @@
|
||||
/**
|
||||
* @file aistatemachine.h
|
||||
* @brief State machine base class
|
||||
*
|
||||
* Copyright (c) 2010, 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.
|
||||
*
|
||||
* 01/03/2010
|
||||
* Initial version, written by Aleric Inglewood @ SL
|
||||
*/
|
||||
|
||||
#ifndef AISTATEMACHINE_H
|
||||
#define AISTATEMACHINE_H
|
||||
|
||||
#include "aithreadsafe.h"
|
||||
#include "llfasttimer.h"
|
||||
#include <boost/signals2.hpp>
|
||||
|
||||
//!
|
||||
// A AIStateMachine is a base class that allows derived classes to
|
||||
// go through asynchronous states, while the code still appears to
|
||||
// be more or less sequential.
|
||||
//
|
||||
// These state machine objects can be reused to build more complex
|
||||
// objects.
|
||||
//
|
||||
// It is important to note that each state has a duality: the object
|
||||
// can have a state that will cause a corresponding function to be
|
||||
// called; and often that function will end with changing the state
|
||||
// again, to signal that it was handled. It is easy to confuse the
|
||||
// function of a state with the state at the end of the function.
|
||||
// For example, the state "initialize" could cause the member
|
||||
// function 'init()' to be called, and at the end one would be
|
||||
// inclined to set the state to "initialized". However, this is the
|
||||
// wrong approach: the correct use of state names does reflect the
|
||||
// functions that will be called, never the function that just was
|
||||
// called.
|
||||
//
|
||||
// Each (derived) class goes through a series of states as follows:
|
||||
//
|
||||
// Creation
|
||||
// |
|
||||
// v
|
||||
// (idle) <----. Idle until run() is called.
|
||||
// | |
|
||||
// Initialize | Calls initialize_impl().
|
||||
// | |
|
||||
// | (idle) | Idle until cont() or set_state() is called.
|
||||
// | | ^ |
|
||||
// v v | |
|
||||
// .-------. | |
|
||||
// | Run |_, | Call multiplex_impl() until idle(), abort() or finish() is called.
|
||||
// '-------' |
|
||||
// | | |
|
||||
// v | |
|
||||
// Abort | | Calls abort_impl().
|
||||
// | | |
|
||||
// v 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.
|
||||
// Finish cleans up whatever is done by Initialize.
|
||||
// Abort should clean up additional things done by Run.
|
||||
//
|
||||
// The Run state is entered by calling run().
|
||||
//
|
||||
// While the base class is in the Run state, it is the derived class
|
||||
// that goes through different states. The state variable of the derived
|
||||
// class is only valid while the base class is in the state Run.
|
||||
//
|
||||
// A derived class can exit the Run state by calling one of two methods:
|
||||
// abort() in case of failure, or finish() in case of success.
|
||||
// Respectively these set the state to Abort and Finish.
|
||||
//
|
||||
// finish_impl may call kill() for a (default) destruction upon finish.
|
||||
// Even in that case the callback (passed to run()) may call run() again,
|
||||
// which overrides the request for a default kill. Or, if finish_impl
|
||||
// doesn't call kill() the callback may call kill() to request the
|
||||
// destruction of the state machine object.
|
||||
//
|
||||
// State machines are run from the "idle" part of the viewer main loop.
|
||||
// Often a state machine has nothing to do however. In that case it can
|
||||
// call the method idle(). This will stop the state machine until
|
||||
// external code changes it's state (by calling set_state()), or calls
|
||||
// cont() to continue with the last state.
|
||||
//
|
||||
// The methods of the derived class call set_state() to change their
|
||||
// own state within the bs_run state, or by calling either abort()
|
||||
// or finish().
|
||||
//
|
||||
// Restarting a finished state machine can also be done by calling run(),
|
||||
// which will cause a re-initialize.
|
||||
//
|
||||
// Derived classes should implement the following constants:
|
||||
//
|
||||
// static state_type const min_state = first_state;
|
||||
// static state_type const max_state = last_state + 1;
|
||||
//
|
||||
// Where first_state should be equal to BaseClass::max_state.
|
||||
// These should represent the minimum and (one past) the maximum
|
||||
// values of mRunState.
|
||||
//
|
||||
// virtual void initialize_impl(void)
|
||||
//
|
||||
// Initializes the derived class.
|
||||
//
|
||||
// virtual void multiplex_impl(void);
|
||||
//
|
||||
// This method should handle mRunState in a switch.
|
||||
// For example:
|
||||
//
|
||||
// switch(mRunState)
|
||||
// {
|
||||
// case foo:
|
||||
// handle_foo();
|
||||
// break;
|
||||
// case wait_state:
|
||||
// if (still_waiting())
|
||||
// {
|
||||
// idle();
|
||||
// break;
|
||||
// }
|
||||
// set_state(working);
|
||||
// /*fall-through*/
|
||||
// case working:
|
||||
// do_work();
|
||||
// if (failure())
|
||||
// abort();
|
||||
// break;
|
||||
// case etc:
|
||||
// etc();
|
||||
// finish();
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// virtual void abort_impl(void);
|
||||
//
|
||||
// A call to this method should bring the object to a state
|
||||
// where finish_impl() can be called.
|
||||
//
|
||||
// virtual void finish_impl(void);
|
||||
//
|
||||
// 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);
|
||||
//
|
||||
// Should return a stringified value of run_state.
|
||||
//
|
||||
class AIStateMachine {
|
||||
//! The type of mState
|
||||
enum base_state_type {
|
||||
bs_initialize,
|
||||
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
|
||||
|
||||
//! Integral value equal to the state with the lowest value.
|
||||
static state_type const min_state = bs_initialize;
|
||||
//! Integral value one more than the state with the highest value.
|
||||
static state_type const max_state = bs_killed + 1;
|
||||
|
||||
private:
|
||||
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().
|
||||
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.
|
||||
LLMutex mIdleActive; //!< Used for atomic operations on the pair mIdle / mActive.
|
||||
#ifdef SHOW_ASSERT
|
||||
apr_os_thread_t mContThread; //!< Thread that last called locked_cont().
|
||||
bool mCalledThreadUnsafeIdle; //!< Set to true when idle() is called.
|
||||
#endif
|
||||
|
||||
// Callback facilities.
|
||||
// From within an other state machine:
|
||||
AIStateMachine* mParent; //!< The parent object that started this state machine, or NULL if there isn't any.
|
||||
state_type mNewParentState; //!< The state at which the parent should continue upon a successful finish.
|
||||
bool mAbortParent; //!< If true, abort parent on abort(). Otherwise continue as normal.
|
||||
bool mOnAbortSignalParent; //!< If true and mAbortParent is false, change state of parent even on abort.
|
||||
// From outside a state machine:
|
||||
struct callback_type {
|
||||
typedef boost::signals2::signal<void (bool)> signal_type;
|
||||
callback_type(signal_type::slot_type const& slot) { connection = signal.connect(slot); }
|
||||
~callback_type() { connection.disconnect(); }
|
||||
void callback(bool success) const { signal(success); }
|
||||
private:
|
||||
boost::signals2::connection connection;
|
||||
signal_type signal;
|
||||
};
|
||||
callback_type* mCallback; //!< Pointer to signal/connection, or NULL when not connected.
|
||||
|
||||
static AIThreadSafeSimpleDC<U64> sMaxCount; //!< Number of cpu clocks below which we start a new state machine within the same frame.
|
||||
|
||||
protected:
|
||||
LLMutex mSetStateLock; //!< For critical areas in set_state() and locked_cont().
|
||||
|
||||
//! State of the derived class. Only valid if mState == bs_run. Call set_state to change.
|
||||
volatile state_type mRunState;
|
||||
|
||||
public:
|
||||
//! Create a non-running state machine.
|
||||
AIStateMachine(void) : mState(bs_initialize), mIdle(true), mAborted(true), mActive(as_idle), mSleep(0), mParent(NULL), mCallback(NULL)
|
||||
#ifdef SHOW_ASSERT
|
||||
, mContThread(0), mCalledThreadUnsafeIdle(false)
|
||||
#endif
|
||||
{ updateSettings(); }
|
||||
|
||||
protected:
|
||||
//! The user should call 'kill()', not delete a AIStateMachine (derived) directly.
|
||||
virtual ~AIStateMachine() { llassert((mState == bs_killed && mActive == as_idle) || mState == bs_initialize); }
|
||||
|
||||
public:
|
||||
//! Halt the state machine until cont() is called (not thread-safe).
|
||||
void idle(void);
|
||||
|
||||
//! Halt the state machine until cont() is called, provided it is still in 'cur_run_state'.
|
||||
void idle(state_type current_run_state);
|
||||
|
||||
//! Temporarily halt the state machine.
|
||||
void yield_frame(unsigned int frames) { mSleep = -(S64)frames; }
|
||||
|
||||
//! Temporarily halt the state machine.
|
||||
void yield_ms(unsigned int ms) { mSleep = LLFastTimer::getCPUClockCount64() + LLFastTimer::countsPerSecond() * ms / 1000; }
|
||||
|
||||
//! Continue running after calling idle.
|
||||
void cont(void)
|
||||
{
|
||||
mSetStateLock.lock();
|
||||
// Ignore calls to cont() if the statemachine isn't idle. See comments in set_state().
|
||||
// Calling cont() twice or after calling set_state(), without first calling idle(), is an error.
|
||||
if (mState != bs_run || !mIdle) { llassert(mState != bs_run || mContThread != apr_os_thread_current()); mSetStateLock.unlock(); return; }
|
||||
locked_cont();
|
||||
}
|
||||
private:
|
||||
void locked_cont(void);
|
||||
|
||||
public:
|
||||
//---------------------------------------
|
||||
// Changing the state.
|
||||
|
||||
//! Change state to <code>bs_run</code>. May only be called after creation or after returning from finish().
|
||||
// If <code>parent</code> is non-NULL, change the parent state machine's state to <code>new_parent_state</code>
|
||||
// upon finish, or in the case of an abort and when <code>abort_parent</code> is true, call parent->abort() instead.
|
||||
void run(AIStateMachine* parent, state_type new_parent_state, bool abort_parent = true, bool on_abort_signal_parent = true);
|
||||
|
||||
//! Change state to 'bs_run'. May only be called after creation or after returning from finish().
|
||||
// Does not cause a callback.
|
||||
void run(void) { run(NULL, 0, false); }
|
||||
|
||||
//! The same as above, but pass the result of a boost::bind with _1.
|
||||
//
|
||||
// Here _1, if present, will be replaced with a bool indicating success.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// <code>
|
||||
// struct Foo { void callback(AIStateMachineDerived* ptr, bool success); };
|
||||
// ...
|
||||
// AIStateMachineDerived* magic = new AIStateMachineDerived; // Deleted by callback
|
||||
// // Call foo_ptr->callback(magic, _1) on finish.
|
||||
// state_machine->run(boost::bind(&Foo::callback, foo_ptr, magic, _1));
|
||||
// </code>
|
||||
//
|
||||
// or
|
||||
//
|
||||
// <code>
|
||||
// struct Foo { void callback(bool success, AIStateMachineDerived const& magic); };
|
||||
// ...
|
||||
// AIStateMachineDerived magic;
|
||||
// // Call foo_ptr->callback(_1, magic) on finish.
|
||||
// magic.run(boost::bind(&Foo::callback, foo_ptr, _1, magic));
|
||||
// </code>
|
||||
//
|
||||
// or
|
||||
//
|
||||
// <code>
|
||||
// static void callback(void* userdata);
|
||||
// ...
|
||||
// AIStateMachineDerived magic;
|
||||
// // Call callback(userdata) on finish.
|
||||
// magic.run(boost::bind(&callback, userdata));
|
||||
// </code>
|
||||
void run(callback_type::signal_type::slot_type const& slot);
|
||||
|
||||
//! Change state to 'bs_abort'. May only be called while in the bs_run state.
|
||||
void abort(void);
|
||||
|
||||
//! Change state to 'bs_finish'. May only be called while in the bs_run state.
|
||||
void finish(void);
|
||||
|
||||
//! Refine state while in the bs_run state. May only be called while in the bs_run state.
|
||||
void set_state(state_type run_state);
|
||||
|
||||
//! Change state to 'bs_killed'. May only be called while in the bs_finish state.
|
||||
void kill(void);
|
||||
|
||||
//---------------------------------------
|
||||
// Other.
|
||||
|
||||
//! Called whenever the StateMachineMaxTime setting is changed.
|
||||
static void updateSettings(void);
|
||||
|
||||
//---------------------------------------
|
||||
// Accessors.
|
||||
|
||||
//! Return true if state machine was aborted (can be used in finish_impl).
|
||||
bool aborted(void) const { return mAborted; }
|
||||
|
||||
//! Return true if the derived class is running (also when we are idle).
|
||||
bool running(void) const { return mState == bs_run; }
|
||||
|
||||
//! Return true if it's safe to call abort.
|
||||
bool abortable(void) const { return mState == bs_run || mState == bs_initialize; }
|
||||
|
||||
//! Return true if the derived class is running but idle.
|
||||
bool waiting(void) const { return mState == bs_run && mIdle; }
|
||||
|
||||
// Use some safebool idiom (http://www.artima.com/cppsource/safebool.html) rather than operator bool.
|
||||
typedef volatile state_type AIStateMachine::* const bool_type;
|
||||
//! Return true if state machine successfully finished.
|
||||
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);
|
||||
|
||||
private:
|
||||
static void add_continued_statemachines(void);
|
||||
static void mainloop(void*);
|
||||
void multiplex(U64 current_time);
|
||||
|
||||
public:
|
||||
//! Abort all running state machines and then run mainloop until all state machines are idle (called when application is exiting).
|
||||
static void flush(void);
|
||||
|
||||
protected:
|
||||
//---------------------------------------
|
||||
// Derived class implementations.
|
||||
|
||||
// Handle initializing the object.
|
||||
virtual void initialize_impl(void) = 0;
|
||||
|
||||
// Handle mRunState.
|
||||
virtual void multiplex_impl(void) = 0;
|
||||
|
||||
// Handle aborting from current bs_run state.
|
||||
virtual void abort_impl(void) = 0;
|
||||
|
||||
// Handle cleaning up from initialization (or post abort) state.
|
||||
virtual void finish_impl(void) = 0;
|
||||
|
||||
// Implemenation of state_str for run states.
|
||||
virtual char const* state_str_impl(state_type run_state) const = 0;
|
||||
};
|
||||
|
||||
// This case be used in state_str_impl.
|
||||
#define AI_CASE_RETURN(x) do { case x: return #x; } while(0)
|
||||
|
||||
#endif
|
||||
@@ -1,96 +0,0 @@
|
||||
/**
|
||||
* @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 <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.
|
||||
*
|
||||
* 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().
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
/**
|
||||
* @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 <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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
Reference in New Issue
Block a user