Merge with VS2017

master
Nicky 2019-11-10 12:07:34 +01:00
commit fee98dac77
42 changed files with 1503 additions and 1485 deletions

View File

@ -542,9 +542,9 @@
<key>archive</key>
<map>
<key>hash</key>
<string>40e8f6c5f56f411db75c19b95db29de9</string>
<string>d453d6200607972493e1797a30fc805f</string>
<key>url</key>
<string>http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/44287/391455/bugsplat-1.0.7.531352-darwin64-531352.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/45455/401027/bugsplat-1.0.7.532004-darwin64-532004.tar.bz2</string>
</map>
<key>name</key>
<string>darwin64</string>
@ -554,9 +554,9 @@
<key>archive</key>
<map>
<key>hash</key>
<string>33a52911164bc6b810b44cdf57be723e</string>
<string>372e1d677ee570f947183bae81ca0852</string>
<key>url</key>
<string>http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/44289/391466/bugsplat-3.6.0.4.531352-windows-531352.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/45457/401043/bugsplat-3.6.0.4.532004-windows-532004.tar.bz2</string>
</map>
<key>name</key>
<string>windows</string>
@ -566,16 +566,16 @@
<key>archive</key>
<map>
<key>hash</key>
<string>8ed50aebd8df656be512fb68be63dc13</string>
<string>261d460a67e3e2f52bdad1391bb02ec3</string>
<key>url</key>
<string>http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/44288/391459/bugsplat-.531352-windows64-531352.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/45456/401036/bugsplat-.532004-windows64-532004.tar.bz2</string>
</map>
<key>name</key>
<string>windows64</string>
</map>
</map>
<key>version</key>
<string>.531352</string>
<string>1.0.7.532004</string>
</map>
<key>colladadom</key>
<map>
@ -857,7 +857,7 @@
</map>
</map>
<key>version</key>
<string>1.500564</string>
<string>1.531288</string>
</map>
<key>dullahan</key>
<map>
@ -1149,6 +1149,18 @@
<key>name</key>
<string>darwin</string>
</map>
<key>darwin64</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>74405aaaa226a9fa13c41d25f8e0df27</string>
<key>url</key>
<string>http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/44202/391132/fmodex-4.44.64.531266-darwin64-531266.tar.bz2</string>
</map>
<key>name</key>
<string>darwin64</string>
</map>
<key>linux</key>
<map>
<key>archive</key>
@ -1166,16 +1178,28 @@
<key>archive</key>
<map>
<key>hash</key>
<string>5b1b5ce866afd2a74e445af1fffe6a8b</string>
<string>2a6d0cb25085cce2f7a4f2c982949eee</string>
<key>url</key>
<string>file:///c:/cygwin/opt/firestorm/fmodex-44461-windows-201601282252-r23.tar.bz2</string>
<string>http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/43834/390171/fmodex-4.44.64.531266-windows-531266.tar.bz2</string>
</map>
<key>name</key>
<string>windows</string>
</map>
<key>windows64</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>bcee653711947af2c8db81d69504704c</string>
<key>url</key>
<string>http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/43833/390164/fmodex-4.44.64.531266-windows64-531266.tar.bz2</string>
</map>
<key>name</key>
<string>windows64</string>
</map>
</map>
<key>version</key>
<string>4.44.61</string>
<string>4.44.64.531266</string>
</map>
<key>fontconfig</key>
<map>
@ -2252,9 +2276,9 @@
<key>archive</key>
<map>
<key>hash</key>
<string>aa2dcce6634256b3280813828e294b58</string>
<string>62b72bf45189b6c876947ac4e8209025</string>
<key>url</key>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44301/391550/libndofdev-0.1.531359-darwin64-531359.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/45899/405069/libndofdev-0.1.532324-darwin64-532324.tar.bz2</string>
</map>
<key>name</key>
<string>darwin64</string>
@ -2264,9 +2288,9 @@
<key>archive</key>
<map>
<key>hash</key>
<string>b9dc910aaba2207d04718b31a84dc10f</string>
<string>28df735378e7ba7154517ebc7bd53a69</string>
<key>url</key>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44307/391585/libndofdev-0.1.531359-windows-531359.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/45901/405083/libndofdev-0.1.532324-windows-532324.tar.bz2</string>
</map>
<key>name</key>
<string>windows</string>
@ -2276,16 +2300,16 @@
<key>archive</key>
<map>
<key>hash</key>
<string>fac91eace685540d467dc24016342cd4</string>
<string>f397f1eb92f5ba75df7b69041156c944</string>
<key>url</key>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44305/391576/libndofdev-0.1.531359-windows64-531359.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/45900/405076/libndofdev-0.1.532324-windows64-532324.tar.bz2</string>
</map>
<key>name</key>
<string>windows64</string>
</map>
</map>
<key>version</key>
<string>0.1.531359</string>
<string>0.1.532324</string>
</map>
<key>libpng</key>
<map>
@ -2921,7 +2945,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
</map>
</map>
<key>version</key>
<string>1.3.3-1.3.6.520171</string>
<string>1.3.3-1.3.6.531357</string>
</map>
<key>open-libndofdev</key>
<map>
@ -3336,9 +3360,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>archive</key>
<map>
<key>hash</key>
<string>f824d586ab5de6edd14ef6828e9e4b66</string>
<string>d1c28eef1aa72d0aaa227e5122b43659</string>
<key>url</key>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44719/395040/slvoice-4.10.0000.32327.5fc3fe7c.531581-darwin64-531581.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/45922/405171/slvoice-4.10.0000.32327.5fc3fe7c.532334-darwin64-532334.tar.bz2</string>
</map>
<key>name</key>
<string>darwin64</string>
@ -3372,9 +3396,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>archive</key>
<map>
<key>hash</key>
<string>1941c17c81905f23b4928288bcf719fb</string>
<string>fcb26f570187eff2c724c3272581628f</string>
<key>url</key>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44720/395047/slvoice-4.10.0000.32327.5fc3fe7c.531581-windows-531581.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/45927/405216/slvoice-4.10.0000.32327.5fc3fe7c.532334-windows-532334.tar.bz2</string>
</map>
<key>name</key>
<string>windows</string>
@ -3384,16 +3408,16 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>archive</key>
<map>
<key>hash</key>
<string>baa6cdc8e8762d5519996ed9faa0bf3f</string>
<string>cf6800e2221ff54d8af1de23b10f2797</string>
<key>url</key>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44721/395056/slvoice-4.10.0000.32327.5fc3fe7c.531581-windows64-531581.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/45926/405209/slvoice-4.10.0000.32327.5fc3fe7c.532334-windows64-532334.tar.bz2</string>
</map>
<key>name</key>
<string>windows64</string>
</map>
</map>
<key>version</key>
<string>4.10.0000.32327.5fc3fe7c.531581</string>
<string>4.10.0000.32327.5fc3fe7c.532334</string>
</map>
<key>tut</key>
<map>
@ -3534,9 +3558,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>archive</key>
<map>
<key>hash</key>
<string>c5ab9d9d7482e48cd76f4bf391900a8c</string>
<string>32fa3efa3529f91fd483c88bc6d64efd</string>
<key>url</key>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/43369/385585/viewer_manager-2.0.531000-darwin64-531000.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44990/397778/viewer_manager-2.0.531762-darwin64-531762.tar.bz2</string>
</map>
<key>name</key>
<string>darwin64</string>
@ -3570,9 +3594,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>archive</key>
<map>
<key>hash</key>
<string>6b10d7407686d9e12e63576256581e3e</string>
<string>49de4f0490b3a3d7b078af0efc8d0ae8</string>
<key>url</key>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/43370/385592/viewer_manager-2.0.531000-windows-531000.tar.bz2</string>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44991/397785/viewer_manager-2.0.531762-windows-531762.tar.bz2</string>
</map>
<key>name</key>
<string>windows</string>
@ -3583,7 +3607,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>source_type</key>
<string>hg</string>
<key>version</key>
<string>2.0.531000</string>
<string>2.0.531762</string>
</map>
<key>vlc-bin</key>
<map>

View File

@ -86,7 +86,7 @@ if(WINDOWS)
elseif (MSVC_VERSION EQUAL 1800) # VisualStudio 2013, which is (sigh) VS 12
list(APPEND LMSVC_VER 120)
list(APPEND LMSVC_VERDOT 12.0)
elseif (MSVC_VERSION EQUAL 1916) # Visual Studio 2017
elseif (MSVC_VERSION EQUAL 1915 OR MSVC_VERSION EQUAL 1916) # Visual Studio 2017
list(APPEND LMSVC_VER 150)
list(APPEND LMSVC_VERDOT 15.0)
else (MSVC80)

View File

@ -5,11 +5,7 @@ use_prebuilt_binary(discord-rpc)
set(DISCORD_INCLUDE_DIRS ${LIBS_PREBUILT_DIR}/include/discord-rpc)
if (WINDOWS)
if (ADDRESS_SIZE EQUAL 32)
set(DISCORD_LIBRARY discord-rpc)
else ()
set(DISCORD_LIBRARY discord-rpc_x64)
endif(ADDRESS_SIZE EQUAL 32)
elseif (LINUX)
set(DISCORD_LIBRARY discord-rpc)
elseif (DARWIN)

View File

@ -1,4 +1,3 @@
# -*- cmake -*-
project(llcommon)
@ -185,7 +184,6 @@ set(llcommon_HEADER_FILES
llkeythrottle.h
llleap.h
llleaplistener.h
lllistenerwrapper.h
llliveappconfig.h
lllivefile.h
llmd5.h

View File

@ -98,7 +98,7 @@
// If VC7 and later, then use the shipped 'dbghelp.h'-file
#pragma pack(push,8)
#if _MSC_VER >= 1300
#pragma warning (push)
#pragma warning (push)
#pragma warning (disable:4091) // a microsoft header has warnings. Very nice.
#include <dbghelp.h>
#pragma warning (pop)
@ -660,7 +660,7 @@ private:
pGMI = (tGMI) GetProcAddress( hPsapi, "GetModuleInformation" );
if ( (pEPM == NULL) || (pGMFNE == NULL) || (pGMBN == NULL) || (pGMI == NULL) )
{
// we couldn´t find all functions
// we couldn't find all functions
FreeLibrary(hPsapi);
return FALSE;
}

View File

@ -54,6 +54,8 @@
#include "google_breakpad/exception_handler.h"
#include "stringize.h"
#include "llcleanup.h"
#include "llevents.h"
#include "llsdutil.h"
//
// Signal handling
@ -586,10 +588,42 @@ void LLApp::runErrorHandler()
LLApp::setStopped();
}
namespace
{
static std::map<LLApp::EAppStatus, const char*> statusDesc
{
{ LLApp::APP_STATUS_RUNNING, "running" },
{ LLApp::APP_STATUS_QUITTING, "quitting" },
{ LLApp::APP_STATUS_STOPPED, "stopped" },
{ LLApp::APP_STATUS_ERROR, "error" }
};
} // anonymous namespace
// static
void LLApp::setStatus(EAppStatus status)
{
sStatus = status;
sStatus = status;
// This can also happen very late in the application lifecycle -- don't
// resurrect a deleted LLSingleton
if (! LLEventPumps::wasDeleted())
{
// notify interested parties of status change
LLSD statsd;
auto found = statusDesc.find(status);
if (found != statusDesc.end())
{
statsd = found->second;
}
else
{
// unknown status? at least report value
statsd = LLSD::Integer(status);
}
LLEventPumps::instance().obtain("LLApp").post(llsd::map("status", statsd));
}
}

View File

@ -23,7 +23,6 @@
* $/LicenseInfo$
*/
// <FS:Ansariel> Fix LNK4221 compiler warning
//#include "llatomic.h"
#include "llatomic.h"
//============================================================================

View File

@ -48,6 +48,7 @@
#undef BOOST_DISABLE_ASSERTS
#endif
// other Linden headers
#include "llapp.h"
#include "lltimer.h"
#include "llevents.h"
#include "llerror.h"
@ -58,10 +59,15 @@
#include <excpt.h>
#endif
const LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller) const
// static
LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller)
{
CoroData* current = mCurrent.get();
CoroData* current{ nullptr };
// be careful about attempted accesses in the final throes of app shutdown
if (! wasDeleted())
{
current = instance().mCurrent.get();
}
// For the main() coroutine, the one NOT explicitly launched by launch(),
// we never explicitly set mCurrent. Use a static CoroData instance with
// canonical values.
@ -70,20 +76,14 @@ const LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller) const
// It's tempting to provide a distinct name for each thread's "main
// coroutine." But as getName() has always returned the empty string
// to mean "not in a coroutine," empty string should suffice here.
static CoroData sMain("");
// We need not reset() the local_ptr to this read-only data: reuse the
// same instance for every thread's main coroutine.
static thread_local CoroData sMain("");
// We need not reset() the local_ptr to this instance; we'll simply
// find it again every time we discover that current is null.
current = &sMain;
}
return *current;
}
LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller)
{
// reuse const implementation, just cast away const-ness of result
return const_cast<CoroData&>(const_cast<const LLCoros*>(this)->get_CoroData(caller));
}
//static
LLCoros::coro::id LLCoros::get_self()
{
@ -93,7 +93,7 @@ LLCoros::coro::id LLCoros::get_self()
//static
void LLCoros::set_consuming(bool consuming)
{
CoroData& data(LLCoros::instance().get_CoroData("set_consuming()"));
CoroData& data(get_CoroData("set_consuming()"));
// DO NOT call this on the main() coroutine.
llassert_always(! data.mName.empty());
data.mConsuming = consuming;
@ -102,22 +102,58 @@ void LLCoros::set_consuming(bool consuming)
//static
bool LLCoros::get_consuming()
{
return LLCoros::instance().get_CoroData("get_consuming()").mConsuming;
return get_CoroData("get_consuming()").mConsuming;
}
// static
void LLCoros::setStatus(const std::string& status)
{
get_CoroData("setStatus()").mStatus = status;
}
// static
std::string LLCoros::getStatus()
{
return get_CoroData("getStatus()").mStatus;
}
LLCoros::LLCoros():
// MAINT-2724: default coroutine stack size too small on Windows.
// Previously we used
// boost::context::guarded_stack_allocator::default_stacksize();
// empirically this is insufficient.
#if ADDRESS_SIZE == 64
mStackSize(512*1024)
mStackSize(512*1024),
#else
mStackSize(256*1024)
mStackSize(256*1024),
#endif
// mCurrent does NOT own the current CoroData instance -- it simply
// points to it. So initialize it with a no-op deleter.
mCurrent{ [](CoroData*){} }
{
}
LLCoros::~LLCoros()
{
printActiveCoroutines("at entry to ~LLCoros()");
// Other LLApp status-change listeners do things like close
// work queues and inject the Stop exception into pending
// promises, to force coroutines waiting on those things to
// notice and terminate. The only problem is that by the time
// LLApp sets "quitting" status, the main loop has stopped
// pumping the fiber scheduler with yield() calls. A waiting
// coroutine still might not wake up until after resources on
// which it depends have been freed. Pump it a few times
// ourselves. Of course, stop pumping as soon as the last of
// the coroutines has terminated.
for (size_t count = 0; count < 10 && CoroData::instanceCount() > 0; ++count)
{
// don't use llcoro::suspend() because that module depends
// on this one
boost::this_fiber::yield();
}
printActiveCoroutines("after pumping");
}
std::string LLCoros::generateDistinctName(const std::string& prefix) const
{
static int unique = 0;
@ -132,7 +168,7 @@ std::string LLCoros::generateDistinctName(const std::string& prefix) const
std::string name(prefix);
// Until we find an unused name, append a numeric suffix for uniqueness.
while (mCoros.find(name) != mCoros.end())
while (CoroData::getInstance(name))
{
name = STRINGIZE(prefix << unique++);
}
@ -166,19 +202,20 @@ void LLCoros::setStackSize(S32 stacksize)
mStackSize = stacksize;
}
void LLCoros::printActiveCoroutines()
void LLCoros::printActiveCoroutines(const std::string& when)
{
LL_INFOS("LLCoros") << "Number of active coroutines: " << (S32)mCoros.size() << LL_ENDL;
if (mCoros.size() > 0)
LL_INFOS("LLCoros") << "Number of active coroutines " << when
<< ": " << CoroData::instanceCount() << LL_ENDL;
if (CoroData::instanceCount() > 0)
{
LL_INFOS("LLCoros") << "-------------- List of active coroutines ------------";
CoroMap::iterator iter;
CoroMap::iterator end = mCoros.end();
F64 time = LLTimer::getTotalSeconds();
for (iter = mCoros.begin(); iter != end; iter++)
for (auto it(CoroData::beginInstances()), end(CoroData::endInstances());
it != end; ++it)
{
F64 life_time = time - iter->second->mCreationTime;
LL_CONT << LL_NEWLINE << "Name: " << iter->first << " life: " << life_time;
F64 life_time = time - it->mCreationTime;
LL_CONT << LL_NEWLINE
<< it->mName << ' ' << it->mStatus << " life: " << life_time;
}
LL_CONT << LL_ENDL;
LL_INFOS("LLCoros") << "-----------------------------------------------------" << LL_ENDL;
@ -244,19 +281,14 @@ void LLCoros::winlevel(const callable_t& callable)
#endif
// Top-level wrapper around caller's coroutine callable.
void LLCoros::toplevel(const std::string& name, const callable_t& callable)
// Normally we like to pass strings and such by const reference -- but in this
// case, we WANT to copy both the name and the callable to our local stack!
void LLCoros::toplevel(std::string name, callable_t callable)
{
CoroData* corodata = new(std::nothrow) CoroData(name);
if (corodata == NULL)
{
// Out of memory?
printActiveCoroutines();
LL_ERRS("LLCoros") << "Failed to start coroutine: " << name << " Stacksize: " << mStackSize << " Total coroutines: " << mCoros.size() << LL_ENDL;
}
// Store it in our pointer map. Oddly, must cast away const-ness of key.
mCoros.insert(const_cast<std::string&>(name), corodata);
// also set it as current
mCurrent.reset(corodata);
// keep the CoroData on this top-level function's stack frame
CoroData corodata(name);
// set it as current
mCurrent.reset(&corodata);
// run the code the caller actually wants in the coroutine
try
@ -265,52 +297,51 @@ void LLCoros::toplevel(const std::string& name, const callable_t& callable)
//#if LL_WINDOWS
// winlevel(callable);
//#else
// </FS:Ansariel>
callable();
//#endif // <FS:Ansariel> Disable for more meaningful callstacks
//#endif
// <FS:Ansariel> Disable for more meaningful callstacks
}
catch (const Stop& exc)
{
LL_INFOS("LLCoros") << "coroutine " << name << " terminating because "
<< exc.what() << LL_ENDL;
}
catch (const LLContinueError&)
{
// Any uncaught exception derived from LLContinueError will be caught
// here and logged. This coroutine will terminate but the rest of the
// viewer will carry on.
LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << corodata->mName));
LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << name));
}
catch (...)
{
// Any OTHER kind of uncaught exception will cause the viewer to
// crash, hopefully informatively.
CRASH_ON_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << name));
}
}
void LLCoros::checkStop()
{
if (wasDeleted())
{
LLTHROW(Shutdown("LLCoros was deleted"));
}
if (LLApp::isStopped())
{
LLTHROW(Stopped("viewer is stopped"));
}
if (! LLApp::isRunning())
{
LLTHROW(Stopping("viewer is stopping"));
}
// <FS:Ansariel> Disable for more meaningful callstacks
//catch (...)
//{
// // Any OTHER kind of uncaught exception will cause the viewer to
// // crash, hopefully informatively.
// CRASH_ON_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << corodata->mName));
//}
// </FS:Ansariel>
}
LLCoros::CoroData::CoroData(const std::string& name):
LLInstanceTracker<CoroData, std::string>(name),
mName(name),
// don't consume events unless specifically directed
mConsuming(false),
mCreationTime(LLTimer::getTotalSeconds())
{
}
void LLCoros::delete_CoroData(CoroData* cdptr)
{
// This custom cleanup function is necessarily static. Find and bind the
// LLCoros instance.
LLCoros& self(LLCoros::instance());
// We set mCurrent on entry to a new fiber, expecting that the
// corresponding entry has already been stored in mCoros. It is an
// error if we do not find that entry.
CoroMap::iterator found = self.mCoros.find(cdptr->mName);
if (found == self.mCoros.end())
{
LL_ERRS("LLCoros") << "Coroutine '" << cdptr->mName << "' terminated "
<< "without being stored in LLCoros::mCoros"
<< LL_ENDL;
}
// Oh good, we found the mCoros entry. Erase it. Because it's a ptr_map,
// that will implicitly delete this CoroData.
self.mCoros.erase(found);
}

View File

@ -29,11 +29,12 @@
#if ! defined(LL_LLCOROS_H)
#define LL_LLCOROS_H
#include "llexception.h"
#include <boost/fiber/fss.hpp>
#include <boost/fiber/future/promise.hpp>
#include <boost/fiber/future/future.hpp>
#include "llsingleton.h"
#include <boost/ptr_container/ptr_map.hpp>
#include "llinstancetracker.h"
#include <boost/function.hpp>
#include <string>
@ -74,6 +75,7 @@
class LL_COMMON_API LLCoros: public LLSingleton<LLCoros>
{
LLSINGLETON(LLCoros);
~LLCoros();
public:
/// The viewer's use of the term "coroutine" became deeply embedded before
/// the industry term "fiber" emerged to distinguish userland threads from
@ -147,8 +149,8 @@ public:
*/
void setStackSize(S32 stacksize);
/// for delayed initialization
void printActiveCoroutines();
/// diagnostic
void printActiveCoroutines(const std::string& when=std::string());
/// get the current coro::id for those who really really care
static coro::id get_self();
@ -176,6 +178,7 @@ public:
{
set_consuming(consuming);
}
OverrideConsuming(const OverrideConsuming&) = delete;
~OverrideConsuming()
{
set_consuming(mPrevConsuming);
@ -185,6 +188,58 @@ public:
bool mPrevConsuming;
};
/// set string coroutine status for diagnostic purposes
static void setStatus(const std::string& status);
static std::string getStatus();
/// RAII control of status
class TempStatus
{
public:
TempStatus(const std::string& status):
mOldStatus(getStatus())
{
setStatus(status);
}
TempStatus(const TempStatus&) = delete;
~TempStatus()
{
setStatus(mOldStatus);
}
private:
std::string mOldStatus;
};
/// thrown by checkStop()
struct Stop: public LLContinueError
{
Stop(const std::string& what): LLContinueError(what) {}
};
/// early stages
struct Stopping: public Stop
{
Stopping(const std::string& what): Stop(what) {}
};
/// cleaning up
struct Stopped: public Stop
{
Stopped(const std::string& what): Stop(what) {}
};
/// cleaned up -- not much survives!
struct Shutdown: public Stop
{
Shutdown(const std::string& what): Stop(what) {}
};
/// Call this intermittently if there's a chance your coroutine might
/// continue running into application shutdown. Throws Stop if LLCoros has
/// been cleaned up.
static void checkStop();
/**
* Aliases for promise and future. An older underlying future implementation
* required us to wrap future; that's no longer needed. However -- if it's
@ -204,18 +259,17 @@ public:
private:
std::string generateDistinctName(const std::string& prefix) const;
void toplevel(const std::string& name, const callable_t& callable);
void toplevel(std::string name, callable_t callable);
struct CoroData;
#if LL_WINDOWS
static void winlevel(const callable_t& callable);
#endif
CoroData& get_CoroData(const std::string& caller);
const CoroData& get_CoroData(const std::string& caller) const;
static CoroData& get_CoroData(const std::string& caller);
S32 mStackSize;
// coroutine-local storage, as it were: one per coro we track
struct CoroData
struct CoroData: public LLInstanceTracker<CoroData, std::string>
{
CoroData(const std::string& name);
@ -223,20 +277,15 @@ private:
const std::string mName;
// set_consuming() state
bool mConsuming;
// setStatus() state
std::string mStatus;
F64 mCreationTime; // since epoch
};
typedef boost::ptr_map<std::string, CoroData> CoroMap;
CoroMap mCoros;
// Identify the current coroutine's CoroData. This local_ptr isn't static
// because it's a member of an LLSingleton, and we rely on it being
// cleaned up in proper dependency order.
// As each coroutine terminates, use our custom cleanup function to remove
// the corresponding entry from mCoros.
local_ptr<CoroData> mCurrent{delete_CoroData};
// Cleanup function for each fiber's instance of mCurrent.
static void delete_CoroData(CoroData* cdptr);
local_ptr<CoroData> mCurrent;
};
namespace llcoro

View File

@ -39,6 +39,8 @@
#if !LL_WINDOWS
# include <syslog.h>
# include <unistd.h>
#else
# include <io.h>
#endif // !LL_WINDOWS
#include <vector>
#include "string.h"
@ -55,6 +57,47 @@
#include "nd/ndlogthrottle.h"
#if LL_WINDOWS
#define fhclose _close
#define fhdup _dup
#define fhdup2 _dup2
#define fhfdopen _fdopen
#define fhfileno _fileno
#else
#define fhclose ::close
#define fhdup ::dup
#define fhdup2 ::dup2
#define fhfdopen ::fdopen
#define fhfileno ::fileno
#endif
namespace LLError
{
class SettingsConfig;
typedef LLPointer<SettingsConfig> SettingsConfigPtr;
class Settings : public LLSingleton<Settings>
{
LLSINGLETON(Settings);
public:
SettingsConfigPtr getSettingsConfig();
~Settings();
void reset();
SettingsStoragePtr saveAndReset();
void restore(SettingsStoragePtr pSettingsStorage);
int getDupStderr() const;
private:
SettingsConfigPtr mSettingsConfig;
int mDupStderr;
};
} // namespace LLError
namespace {
#if LL_WINDOWS
void debugger_print(const std::string& s)
@ -120,79 +163,88 @@ namespace {
class RecordToFile : public LLError::Recorder
{
public:
RecordToFile(const std::string& filename)
RecordToFile(const std::string& filename):
mName(filename),
mFile(LLFile::fopen(filename, "a")),
mSavedStderr(LLError::Settings::instance().getDupStderr())
{
// <FS:Ansariel> Don't screw up log file output
this->showMultiline(true);
mFile.open(filename.c_str(), std::ios_base::out | std::ios_base::app);
if (!mFile)
{
LL_INFOS() << "Error setting log file to " << filename << LL_ENDL;
LL_WARNS() << "Error setting log file to " << filename << LL_ENDL;
}
else
{
// We use a number of classic-C libraries, some of which write
// log output to stderr. The trouble with that is that unless
// you launch the viewer from a console, stderr output is
// lost. Redirect STDERR_FILENO to write into this log file.
fhdup2(fhfileno(mFile), fhfileno(stderr));
}
else
{
if (!LLError::getAlwaysFlush())
{
mFile.sync_with_stdio(false);
}
}
}
~RecordToFile()
{
// restore stderr to its original fileno so any subsequent output
// to stderr goes to original stream
fhdup2(mSavedStderr, fhfileno(stderr));
mFile.close();
}
virtual bool enabled() override
{
virtual bool enabled() override
{
#ifdef LL_RELEASE_FOR_DOWNLOAD
return 1;
return 1;
#else
return LLError::getEnabledLogTypesMask() & 0x02;
return LLError::getEnabledLogTypesMask() & 0x02;
#endif
}
bool okay() { return mFile.good(); }
}
bool okay() const { return bool(mFile); }
std::string getFilename() const { return mName; }
virtual void recordMessage(LLError::ELevel level,
const std::string& message) override
{
if (LLError::getAlwaysFlush())
{
mFile << message << std::endl;
}
else
{
mFile << message << "\n";
}
::fwrite(message.c_str(), sizeof(char), message.length(), mFile);
::fputc('\n', mFile);
if (LLError::getAlwaysFlush())
{
::fflush(mFile);
}
}
private:
llofstream mFile;
const std::string mName;
LLUniqueFile mFile;
int mSavedStderr;
};
class RecordToStderr : public LLError::Recorder
{
public:
RecordToStderr(bool timestamp) : mUseANSI(ANSI_PROBE)
RecordToStderr(bool timestamp) :
mUseANSI(checkANSI()),
// use duplicate stderr file handle so THIS output isn't affected
// by our internal redirection of all (other) stderr output
mStderr(fhfdopen(LLError::Settings::instance().getDupStderr(), "a"))
{
this->showMultiline(true);
this->showMultiline(true);
}
virtual bool enabled() override
{
return LLError::getEnabledLogTypesMask() & 0x04;
}
virtual bool enabled() override
{
return LLError::getEnabledLogTypesMask() & 0x04;
}
virtual void recordMessage(LLError::ELevel level,
const std::string& message) override
{
if (ANSI_PROBE == mUseANSI)
mUseANSI = (checkANSI() ? ANSI_YES : ANSI_NO);
if (ANSI_YES == mUseANSI)
if (mUseANSI)
{
// Default all message levels to bold so we can distinguish our own messages from those dumped by subprocesses and libraries.
colorANSI("1"); // bold
@ -210,34 +262,30 @@ namespace {
break;
}
}
fprintf(stderr, "%s\n", message.c_str());
fprintf(mStderr, "%s\n", message.c_str());
#if LL_WINDOWS
fflush(stderr); //Now using a buffer. flush is required.
fflush(mStderr); //Now using a buffer. flush is required.
#endif
if (ANSI_YES == mUseANSI) colorANSI("0"); // reset
if (mUseANSI) colorANSI("0"); // reset
}
private:
enum ANSIState
{
ANSI_PROBE,
ANSI_YES,
ANSI_NO
} mUseANSI;
bool mUseANSI;
LLFILE* mStderr;
void colorANSI(const std::string color)
{
// ANSI color code escape sequence
fprintf(stderr, "\033[%sm", color.c_str() );
fprintf(mStderr, "\033[%sm", color.c_str() );
};
bool checkANSI(void)
static bool checkANSI(void)
{
#if LL_LINUX || LL_DARWIN
// Check whether it's okay to use ANSI; if stderr is
// a tty then we assume yes. Can be turned off with
// the LL_NO_ANSI_COLOR env var.
return (0 != isatty(2)) &&
return (0 != isatty(fhfileno(stderr))) &&
(NULL == getenv("LL_NO_ANSI_COLOR"));
#endif // LL_LINUX
return false;
@ -492,34 +540,15 @@ namespace LLError
LLError::FatalFunction mCrashFunction;
LLError::TimeFunction mTimeFunction;
Recorders mRecorders;
RecorderPtr mFileRecorder;
RecorderPtr mFixedBufferRecorder;
std::string mFileRecorderFileName;
int mShouldLogCallCounter;
int mShouldLogCallCounter;
private:
SettingsConfig();
};
typedef LLPointer<SettingsConfig> SettingsConfigPtr;
class Settings : public LLSingleton<Settings>
{
LLSINGLETON(Settings);
public:
SettingsConfigPtr getSettingsConfig();
void reset();
SettingsStoragePtr saveAndReset();
void restore(SettingsStoragePtr pSettingsStorage);
private:
SettingsConfigPtr mSettingsConfig;
};
SettingsConfig::SettingsConfig()
: LLRefCount(),
mDefaultLevel(LLError::LEVEL_DEBUG),
@ -533,9 +562,6 @@ namespace LLError
mCrashFunction(NULL),
mTimeFunction(NULL),
mRecorders(),
mFileRecorder(),
mFixedBufferRecorder(),
mFileRecorderFileName(),
mShouldLogCallCounter(0)
{
}
@ -546,10 +572,20 @@ namespace LLError
}
Settings::Settings():
mSettingsConfig(new SettingsConfig())
mSettingsConfig(new SettingsConfig()),
// duplicate stderr file handle right away
mDupStderr(fhdup(fhfileno(stderr)))
{
}
Settings::~Settings()
{
// restore original stderr
fhdup2(mDupStderr, fhfileno(stderr));
// and close the duplicate
fhclose(mDupStderr);
}
SettingsConfigPtr Settings::getSettingsConfig()
{
return mSettingsConfig;
@ -575,6 +611,11 @@ namespace LLError
mSettingsConfig = newSettingsConfig;
}
int Settings::getDupStderr() const
{
return mDupStderr;
}
bool is_available()
{
return Settings::instanceExists() && Globals::instanceExists();
@ -693,20 +734,19 @@ namespace
void commonInit(const std::string& user_dir, const std::string& app_dir, bool log_to_stderr = true)
{
LLError::Settings::getInstance()->reset();
LLError::setDefaultLevel(LLError::LEVEL_INFO);
LLError::setAlwaysFlush(true);
LLError::setEnabledLogTypesMask(0xFFFFFFFF);
LLError::setAlwaysFlush(true);
LLError::setEnabledLogTypesMask(0xFFFFFFFF);
LLError::setFatalFunction(LLError::crashAndLoop);
LLError::setTimeFunction(LLError::utcTime);
// log_to_stderr is only false in the unit and integration tests to keep builds quieter
if (log_to_stderr && shouldLogToStderr())
{
LLError::RecorderPtr recordToStdErr(new RecordToStderr(stderrLogWantsTime()));
LLError::addRecorder(recordToStdErr);
LLError::logToStderr();
}
#if LL_WINDOWS
LLError::RecorderPtr recordToWinDebug(new RecordToWinDebug());
LLError::addRecorder(recordToWinDebug);
@ -1004,49 +1044,110 @@ namespace LLError
s->mRecorders.erase(std::remove(s->mRecorders.begin(), s->mRecorders.end(), recorder),
s->mRecorders.end());
}
// Find an entry in SettingsConfig::mRecorders whose RecorderPtr points to
// a Recorder subclass of type RECORDER. Return, not a RecorderPtr (which
// points to the Recorder base class), but a shared_ptr<RECORDER> which
// specifically points to the concrete RECORDER subclass instance, along
// with a Recorders::iterator indicating the position of that entry in
// mRecorders. The shared_ptr might be empty (operator!() returns true) if
// there was no such RECORDER subclass instance in mRecorders.
template <typename RECORDER>
std::pair<boost::shared_ptr<RECORDER>, Recorders::iterator>
findRecorderPos()
{
SettingsConfigPtr s = Settings::instance().getSettingsConfig();
// Since we promise to return an iterator, use a classic iterator
// loop.
auto end{s->mRecorders.end()};
for (Recorders::iterator it{s->mRecorders.begin()}; it != end; ++it)
{
// *it is a RecorderPtr, a shared_ptr<Recorder>. Use a
// dynamic_pointer_cast to try to downcast to test if it's also a
// shared_ptr<RECORDER>.
auto ptr = boost::dynamic_pointer_cast<RECORDER>(*it);
if (ptr)
{
// found the entry we want
return { ptr, it };
}
}
// dropped out of the loop without finding any such entry -- instead
// of default-constructing Recorders::iterator (which might or might
// not be valid), return a value that is valid but not dereferenceable.
return { {}, end };
}
// Find an entry in SettingsConfig::mRecorders whose RecorderPtr points to
// a Recorder subclass of type RECORDER. Return, not a RecorderPtr (which
// points to the Recorder base class), but a shared_ptr<RECORDER> which
// specifically points to the concrete RECORDER subclass instance. The
// shared_ptr might be empty (operator!() returns true) if there was no
// such RECORDER subclass instance in mRecorders.
template <typename RECORDER>
boost::shared_ptr<RECORDER> findRecorder()
{
return findRecorderPos<RECORDER>().first;
}
// Remove an entry from SettingsConfig::mRecorders whose RecorderPtr
// points to a Recorder subclass of type RECORDER. Return true if there
// was one and we removed it, false if there wasn't one to start with.
template <typename RECORDER>
bool removeRecorder()
{
auto found = findRecorderPos<RECORDER>();
if (found.first)
{
SettingsConfigPtr s = Settings::instance().getSettingsConfig();
s->mRecorders.erase(found.second);
}
return bool(found.first);
}
}
namespace LLError
{
void logToFile(const std::string& file_name)
{
SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
// remove any previous Recorder filling this role
removeRecorder<RecordToFile>();
removeRecorder(s->mFileRecorder);
s->mFileRecorder.reset();
s->mFileRecorderFileName.clear();
if (!file_name.empty())
{
RecorderPtr recordToFile(new RecordToFile(file_name));
if (boost::dynamic_pointer_cast<RecordToFile>(recordToFile)->okay())
{
s->mFileRecorderFileName = file_name;
s->mFileRecorder = recordToFile;
addRecorder(recordToFile);
}
boost::shared_ptr<RecordToFile> recordToFile(new RecordToFile(file_name));
if (recordToFile->okay())
{
addRecorder(recordToFile);
}
}
}
void logToFixedBuffer(LLLineBuffer* fixedBuffer)
{
SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
removeRecorder(s->mFixedBufferRecorder);
s->mFixedBufferRecorder.reset();
if (fixedBuffer)
{
RecorderPtr recordToFixedBuffer(new RecordToFixedBuffer(fixedBuffer));
s->mFixedBufferRecorder = recordToFixedBuffer;
addRecorder(recordToFixedBuffer);
}
}
std::string logFileName()
{
SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
return s->mFileRecorderFileName;
auto found = findRecorder<RecordToFile>();
return found? found->getFilename() : std::string();
}
void logToStderr()
{
if (! findRecorder<RecordToStderr>())
{
RecorderPtr recordToStdErr(new RecordToStderr(stderrLogWantsTime()));
addRecorder(recordToStdErr);
}
}
void logToFixedBuffer(LLLineBuffer* fixedBuffer)
{
// remove any previous Recorder filling this role
removeRecorder<RecordToFixedBuffer>();
if (fixedBuffer)
{
RecorderPtr recordToFixedBuffer(new RecordToFixedBuffer(fixedBuffer));
addRecorder(recordToFixedBuffer);
}
}
}

View File

@ -218,19 +218,6 @@ namespace LLError
const char** tags,
size_t tag_count);
#ifdef LL_LINUX
// <FS:ND> Temp hack to get the old linux havok stub to link
CallSite(LLError::ELevel,
char const*,
int,
std::type_info const&,
char const*,
char const*,
char const*,
bool);
// </FS:ND>
#endif
~CallSite();
#ifdef LL_LIBRARY_INCLUDE

View File

@ -183,6 +183,7 @@ namespace LLError
// each error message is passed to each recorder via recordMessage()
LL_COMMON_API void logToFile(const std::string& filename);
LL_COMMON_API void logToStderr();
LL_COMMON_API void logToFixedBuffer(LLLineBuffer*);
// Utilities to add recorders for logging to a file or a fixed buffer
// A second call to the same function will remove the logger added

View File

@ -32,6 +32,7 @@
#include "lleventcoro.h"
// STL headers
#include <chrono>
#include <exception>
// std headers
// external library headers
#include <boost/fiber/operations.hpp>
@ -39,6 +40,7 @@
#include "llsdserialize.h"
#include "llerror.h"
#include "llcoros.h"
#include "stringize.h"
namespace
{
@ -146,70 +148,123 @@ void storeToLLSDPath(LLSD& dest, const LLSD& rawPath, const LLSD& value)
void llcoro::suspend()
{
LLCoros::checkStop();
LLCoros::TempStatus st("waiting one tick");
boost::this_fiber::yield();
}
void llcoro::suspendUntilTimeout(float seconds)
{
LLCoros::checkStop();
// The fact that we accept non-integer seconds means we should probably
// use granularity finer than one second. However, given the overhead of
// the rest of our processing, it seems silly to use granularity finer
// than a millisecond.
LLCoros::TempStatus st(STRINGIZE("waiting for " << seconds << "s"));
boost::this_fiber::sleep_for(std::chrono::milliseconds(long(seconds * 1000)));
}
namespace
{
LLBoundListener postAndSuspendSetup(const std::string& callerName,
const std::string& listenerName,
LLCoros::Promise<LLSD>& promise,
const LLSD& event,
const LLEventPumpOrPumpName& requestPump,
const LLEventPumpOrPumpName& replyPump,
const LLSD& replyPumpNamePath)
// returns a listener on replyPumpP, also on "mainloop" -- both should be
// stored in LLTempBoundListeners on the caller's stack frame
std::pair<LLBoundListener, LLBoundListener>
postAndSuspendSetup(const std::string& callerName,
const std::string& listenerName,
LLCoros::Promise<LLSD>& promise,
const LLSD& event,
const LLEventPumpOrPumpName& requestPumpP,
const LLEventPumpOrPumpName& replyPumpP,
const LLSD& replyPumpNamePath)
{
// Before we get any farther -- should we be stopping instead of
// suspending?
LLCoros::checkStop();
// Get the consuming attribute for THIS coroutine, the one that's about to
// suspend. Don't call get_consuming() in the lambda body: that would
// return the consuming attribute for some other coroutine, most likely
// the main routine.
bool consuming(LLCoros::get_consuming());
// make a callback that will assign a value to the future, and listen on
// the specified LLEventPump with that callback
// listen on the specified LLEventPump with a lambda that will assign a
// value to the promise, thus fulfilling its future
llassert_always_msg(replyPumpP, ("replyPump required for " + callerName));
LLEventPump& replyPump(replyPumpP.getPump());
// The relative order of the two listen() calls below would only matter if
// "LLApp" were an LLEventMailDrop. But if we ever go there, we'd want to
// notice the pending LLApp status first.
LLBoundListener stopper(
LLEventPumps::instance().obtain("LLApp").listen(
listenerName,
[&promise, listenerName](const LLSD& status)
{
// anything except "running" should wake up the waiting
// coroutine
auto& statsd = status["status"];
if (statsd.asString() != "running")
{
LL_DEBUGS("lleventcoro") << listenerName
<< " spotted status " << statsd
<< ", throwing Stopping" << LL_ENDL;
try
{
promise.set_exception(
std::make_exception_ptr(
LLCoros::Stopping("status " + statsd.asString())));
}
catch (const boost::fibers::promise_already_satisfied&)
{
LL_WARNS("lleventcoro") << listenerName
<< " couldn't throw Stopping "
"because promise already set" << LL_ENDL;
}
}
// do not consume -- every listener must see status
return false;
}));
LLBoundListener connection(
replyPump.getPump().listen(listenerName,
[&promise, consuming, listenerName](const LLSD& result)
{
try
{
promise.set_value(result);
}
catch(boost::fibers::promise_already_satisfied & ex)
{
LL_WARNS("lleventcoro") << "promise already satisfied in '"
<< listenerName << "' " << ex.what() << LL_ENDL;
}
return consuming;
}));
replyPump.listen(
listenerName,
[&promise, consuming, listenerName](const LLSD& result)
{
try
{
promise.set_value(result);
// We did manage to propagate the result value to the
// (real) listener. If we're supposed to indicate that
// we've consumed it, do so.
return consuming;
}
catch(boost::fibers::promise_already_satisfied & ex)
{
LL_DEBUGS("lleventcoro") << "promise already satisfied in '"
<< listenerName << "': " << ex.what() << LL_ENDL;
// We could not propagate the result value to the
// listener.
return false;
}
}));
// skip the "post" part if requestPump is default-constructed
if (requestPump)
if (requestPumpP)
{
LLEventPump& requestPump(requestPumpP.getPump());
// If replyPumpNamePath is non-empty, store the replyPump name in the
// request event.
LLSD modevent(event);
storeToLLSDPath(modevent, replyPumpNamePath, replyPump.getPump().getName());
storeToLLSDPath(modevent, replyPumpNamePath, replyPump.getName());
LL_DEBUGS("lleventcoro") << callerName << ": coroutine " << listenerName
<< " posting to " << requestPump.getPump().getName()
<< " posting to " << requestPump.getName()
<< LL_ENDL;
// *NOTE:Mani - Removed because modevent could contain user's hashed passwd.
// << ": " << modevent << LL_ENDL;
requestPump.getPump().post(modevent);
requestPump.post(modevent);
}
LL_DEBUGS("lleventcoro") << callerName << ": coroutine " << listenerName
<< " about to wait on LLEventPump " << replyPump.getPump().getName()
<< " about to wait on LLEventPump " << replyPump.getName()
<< LL_ENDL;
return connection;
return { connection, stopper };
}
} // anonymous
@ -220,15 +275,17 @@ LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requ
LLCoros::Promise<LLSD> promise;
std::string listenerName(listenerNameForCoro());
// Store connection into an LLTempBoundListener so we implicitly
// Store both connections into LLTempBoundListeners so we implicitly
// disconnect on return from this function.
LLTempBoundListener connection =
auto connections =
postAndSuspendSetup("postAndSuspend()", listenerName, promise,
event, requestPump, replyPump, replyPumpNamePath);
LLTempBoundListener connection(connections.first), stopper(connections.second);
// declare the future
LLCoros::Future<LLSD> future = LLCoros::getFuture(promise);
// calling get() on the future makes us wait for it
LLCoros::TempStatus st(STRINGIZE("waiting for " << replyPump.getPump().getName()));
LLSD value(future.get());
LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName
<< " resuming with " << value << LL_ENDL;
@ -245,17 +302,22 @@ LLSD llcoro::postAndSuspendWithTimeout(const LLSD& event,
LLCoros::Promise<LLSD> promise;
std::string listenerName(listenerNameForCoro());
// Store connection into an LLTempBoundListener so we implicitly
// Store both connections into LLTempBoundListeners so we implicitly
// disconnect on return from this function.
LLTempBoundListener connection =
auto connections =
postAndSuspendSetup("postAndSuspendWithTimeout()", listenerName, promise,
event, requestPump, replyPump, replyPumpNamePath);
LLTempBoundListener connection(connections.first), stopper(connections.second);
// declare the future
LLCoros::Future<LLSD> future = LLCoros::getFuture(promise);
// wait for specified timeout
boost::fibers::future_status status =
future.wait_for(std::chrono::milliseconds(long(timeout * 1000)));
boost::fibers::future_status status;
{
LLCoros::TempStatus st(STRINGIZE("waiting for " << replyPump.getPump().getName()
<< " for " << timeout << "s"));
status = future.wait_for(std::chrono::milliseconds(long(timeout * 1000)));
}
// if the future is NOT yet ready, return timeoutResult instead
if (status == boost::fibers::future_status::timeout)
{

View File

@ -37,6 +37,9 @@
// other Linden headers
#include "llerror.h" // LL_ERRS
#include "llsdutil.h" // llsd_matches()
#include "stringize.h"
#include "lleventtimer.h"
#include "lldate.h"
/*****************************************************************************
* LLEventFilter
@ -182,6 +185,27 @@ bool LLEventTimeout::countdownElapsed() const
return mTimer.hasExpired();
}
LLEventTimer* LLEventTimeout::post_every(F32 period, const std::string& pump, const LLSD& data)
{
return LLEventTimer::run_every(
period,
[pump, data](){ LLEventPumps::instance().obtain(pump).post(data); });
}
LLEventTimer* LLEventTimeout::post_at(const LLDate& time, const std::string& pump, const LLSD& data)
{
return LLEventTimer::run_at(
time,
[pump, data](){ LLEventPumps::instance().obtain(pump).post(data); });
}
LLEventTimer* LLEventTimeout::post_after(F32 interval, const std::string& pump, const LLSD& data)
{
return LLEventTimer::run_after(
interval,
[pump, data](){ LLEventPumps::instance().obtain(pump).post(data); });
}
/*****************************************************************************
* LLEventBatch
*****************************************************************************/
@ -409,3 +433,61 @@ void LLEventBatchThrottle::setSize(std::size_t size)
flush();
}
}
/*****************************************************************************
* LLEventLogProxy
*****************************************************************************/
LLEventLogProxy::LLEventLogProxy(LLEventPump& source, const std::string& name, bool tweak):
// note: we are NOT using the constructor that implicitly connects!
LLEventFilter(name, tweak),
// instead we simply capture a reference to the subject LLEventPump
mPump(source)
{
}
bool LLEventLogProxy::post(const LLSD& event) /* override */
{
auto counter = mCounter++;
auto eventplus = event;
if (eventplus.type() == LLSD::TypeMap)
{
eventplus["_cnt"] = counter;
}
std::string hdr{STRINGIZE(getName() << ": post " << counter)};
LL_INFOS("LogProxy") << hdr << ": " << event << LL_ENDL;
bool result = mPump.post(eventplus);
LL_INFOS("LogProxy") << hdr << " => " << result << LL_ENDL;
return result;
}
LLBoundListener LLEventLogProxy::listen_impl(const std::string& name,
const LLEventListener& target,
const NameList& after,
const NameList& before)
{
LL_DEBUGS("LogProxy") << "LLEventLogProxy('" << getName() << "').listen('"
<< name << "')" << LL_ENDL;
return mPump.listen(name,
[this, name, target](const LLSD& event)->bool
{ return listener(name, target, event); },
after,
before);
}
bool LLEventLogProxy::listener(const std::string& name,
const LLEventListener& target,
const LLSD& event) const
{
auto eventminus = event;
std::string counter{"**"};
if (eventminus.has("_cnt"))
{
counter = stringize(eventminus["_cnt"].asInteger());
eventminus.erase("_cnt");
}
std::string hdr{STRINGIZE(getName() << " to " << name << " " << counter)};
LL_INFOS("LogProxy") << hdr << ": " << eventminus << LL_ENDL;
bool result = target(eventminus);
LL_INFOS("LogProxy") << hdr << " => " << result << LL_ENDL;
return result;
}

View File

@ -34,6 +34,9 @@
#include "lltimer.h"
#include <boost/function.hpp>
class LLEventTimer;
class LLDate;
/**
* Generic base class
*/
@ -210,6 +213,19 @@ public:
LLEventTimeout();
LLEventTimeout(LLEventPump& source);
/// using LLEventTimeout as namespace for free functions
/// Post event to specified LLEventPump every period seconds. Delete
/// returned LLEventTimer* to cancel.
static LLEventTimer* post_every(F32 period, const std::string& pump, const LLSD& data);
/// Post event to specified LLEventPump at specified future time. Call
/// LLEventTimer::getInstance(returned pointer) to check whether it's still
/// pending; if so, delete the pointer to cancel.
static LLEventTimer* post_at(const LLDate& time, const std::string& pump, const LLSD& data);
/// Post event to specified LLEventPump after specified interval. Call
/// LLEventTimer::getInstance(returned pointer) to check whether it's still
/// pending; if so, delete the pointer to cancel.
static LLEventTimer* post_after(F32 interval, const std::string& pump, const LLSD& data);
protected:
virtual void setCountdown(F32 seconds);
virtual bool countdownElapsed() const;
@ -376,4 +392,99 @@ private:
std::size_t mBatchSize;
};
/*****************************************************************************
* LLEventLogProxy
*****************************************************************************/
/**
* LLEventLogProxy is a little different than the other LLEventFilter
* subclasses declared in this header file, in that it completely wraps the
* passed LLEventPump (both input and output) instead of simply processing its
* output. Of course, if someone directly posts to the wrapped LLEventPump by
* looking up its string name in LLEventPumps, LLEventLogProxy can't intercept
* that post() call. But as long as consuming code is willing to access the
* LLEventLogProxy instance instead of the wrapped LLEventPump, all event data
* both post()ed and received is logged.
*
* The proxy role means that LLEventLogProxy intercepts more of LLEventPump's
* API than a typical LLEventFilter subclass.
*/
class LLEventLogProxy: public LLEventFilter
{
typedef LLEventFilter super;
public:
/**
* Construct LLEventLogProxy, wrapping the specified LLEventPump.
* Unlike a typical LLEventFilter subclass, the name parameter is @emph
* not optional because typically you want LLEventLogProxy to completely
* replace the wrapped LLEventPump. So you give the subject LLEventPump
* some other name and give the LLEventLogProxy the name that would have
* been used for the subject LLEventPump.
*/
LLEventLogProxy(LLEventPump& source, const std::string& name, bool tweak=false);
/// register a new listener
LLBoundListener listen_impl(const std::string& name, const LLEventListener& target,
const NameList& after, const NameList& before);
/// Post an event to all listeners
virtual bool post(const LLSD& event) /* override */;
private:
/// This method intercepts each call to any target listener. We pass it
/// the listener name and the caller's intended target listener plus the
/// posted LLSD event.
bool listener(const std::string& name,
const LLEventListener& target,
const LLSD& event) const;
LLEventPump& mPump;
LLSD::Integer mCounter{0};
};
/**
* LLEventPumpHolder<T> is a helper for LLEventLogProxyFor<T>. It simply
* stores an instance of T, presumably a subclass of LLEventPump. We derive
* LLEventLogProxyFor<T> from LLEventPumpHolder<T>, ensuring that
* LLEventPumpHolder's contained mWrappedPump is fully constructed before
* passing it to LLEventLogProxyFor's LLEventLogProxy base class constructor.
* But since LLEventPumpHolder<T> presents none of the LLEventPump API,
* LLEventLogProxyFor<T> inherits its methods unambiguously from
* LLEventLogProxy.
*/
template <class T>
class LLEventPumpHolder
{
protected:
LLEventPumpHolder(const std::string& name, bool tweak=false):
mWrappedPump(name, tweak)
{}
T mWrappedPump;
};
/**
* LLEventLogProxyFor<T> is a wrapper around any of the LLEventPump subclasses.
* Instantiating an LLEventLogProxy<T> instantiates an internal T. Otherwise
* it behaves like LLEventLogProxy.
*/
template <class T>
class LLEventLogProxyFor: private LLEventPumpHolder<T>, public LLEventLogProxy
{
// We derive privately from LLEventPumpHolder because it's an
// implementation detail of LLEventLogProxyFor. The only reason it's a
// base class at all is to guarantee that it's constructed first so we can
// pass it to our LLEventLogProxy base class constructor.
typedef LLEventPumpHolder<T> holder;
typedef LLEventLogProxy super;
public:
LLEventLogProxyFor(const std::string& name, bool tweak=false):
// our wrapped LLEventPump subclass instance gets a name suffix
// because that's not the LLEventPump we want consumers to obtain when
// they ask LLEventPumps for this name
holder(name + "-", tweak),
// it's our LLEventLogProxy that gets the passed name
super(holder::mWrappedPump, name, tweak)
{}
};
#endif /* ! defined(LL_LLEVENTFILTER_H) */

View File

@ -63,52 +63,24 @@
#pragma warning (disable : 4702)
#endif
/*****************************************************************************
* queue_names: specify LLEventPump names that should be instantiated as
* LLEventQueue
*****************************************************************************/
/**
* At present, we recognize particular requested LLEventPump names as needing
* LLEventQueues. Later on we'll migrate this information to an external
* configuration file.
*/
const char* queue_names[] =
{
"placeholder - replace with first real name string"
};
/*****************************************************************************
* If there's a "mainloop" pump, listen on that to flush all LLEventQueues
*****************************************************************************/
struct RegisterFlush : public LLEventTrackable
{
RegisterFlush():
pumps(LLEventPumps::instance())
{
pumps.obtain("mainloop").listen("flushLLEventQueues", boost::bind(&RegisterFlush::flush, this, _1));
}
bool flush(const LLSD&)
{
pumps.flush();
return false;
}
~RegisterFlush()
{
// LLEventTrackable handles stopListening for us.
}
LLEventPumps& pumps;
};
static RegisterFlush registerFlush;
/*****************************************************************************
* LLEventPumps
*****************************************************************************/
LLEventPumps::LLEventPumps():
// Until we migrate this information to an external config file,
// initialize mQueueNames from the static queue_names array.
mQueueNames(boost::begin(queue_names), boost::end(queue_names))
{
}
mFactories
{
{ "LLEventStream", [](const std::string& name, bool tweak)
{ return new LLEventStream(name, tweak); } },
{ "LLEventMailDrop", [](const std::string& name, bool tweak)
{ return new LLEventMailDrop(name, tweak); } }
},
mTypes
{
// LLEventStream is the default for obtain(), so even if somebody DOES
// call obtain("placeholder"), this sample entry won't break anything.
{ "placeholder", "LLEventStream" }
}
{}
LLEventPump& LLEventPumps::obtain(const std::string& name)
{
@ -119,14 +91,31 @@ LLEventPump& LLEventPumps::obtain(const std::string& name)
// name.
return *found->second;
}
// Here we must instantiate an LLEventPump subclass.
LLEventPump* newInstance;
// Should this name be an LLEventQueue?
PumpNames::const_iterator nfound = mQueueNames.find(name);
if (nfound != mQueueNames.end())
newInstance = new LLEventQueue(name);
else
newInstance = new LLEventStream(name);
// Here we must instantiate an LLEventPump subclass. Is there a
// preregistered class name override for this specific instance name?
auto nfound = mTypes.find(name);
std::string type;
if (nfound != mTypes.end())
{
type = nfound->second;
}
// pass tweak=false: we already know there's no existing instance with
// this name
return make(name, false, type);
}
LLEventPump& LLEventPumps::make(const std::string& name, bool tweak,
const std::string& type)
{
// find the relevant factory for this (or default) type
auto found = mFactories.find(type.empty()? "LLEventStream" : type);
if (found == mFactories.end())
{
// Passing an unrecognized type name is a no-no
LLTHROW(BadType(type));
}
auto newInstance = (found->second)(name, tweak);
// LLEventPump's constructor implicitly registers each new instance in
// mPumpMap. But remember that we instantiated it (in mOurPumps) so we'll
// delete it later.
@ -144,14 +133,13 @@ bool LLEventPumps::post(const std::string&name, const LLSD&message)
return (*found).second->post(message);
}
void LLEventPumps::flush()
{
// Flush every known LLEventPump instance. Leave it up to each instance to
// decide what to do with the flush() call.
for (PumpMap::iterator pmi = mPumpMap.begin(), pmend = mPumpMap.end(); pmi != pmend; ++pmi)
for (PumpMap::value_type& pair : mPumpMap)
{
pmi->second->flush();
pair.second->flush();
}
}
@ -165,6 +153,9 @@ void LLEventPumps::clear()
}
}
// Clear every known LLEventPump instance. Leave it up to each instance to
// decide what to do with the clear() call.
void LLEventPumps::reset()
{
// Reset every known LLEventPump instance. Leave it up to each instance to
@ -278,6 +269,9 @@ LLEventPumps::~LLEventPumps()
{
delete *mOurPumps.begin();
}
// Reset every remaining registered LLEventPump subclass instance: those
// we DIDN'T instantiate using either make() or obtain().
reset();
}
/*****************************************************************************
@ -603,46 +597,9 @@ LLBoundListener LLEventMailDrop::listen_impl(const std::string& name,
return LLEventStream::listen_impl(name, listener, after, before);
}
/*****************************************************************************
* LLEventQueue
*****************************************************************************/
bool LLEventQueue::post(const LLSD& event)
void LLEventMailDrop::discard()
{
if (mEnabled)
{
// Defer sending this event by queueing it until flush()
mEventQueue.push_back(event);
}
// Unconditionally return false. We won't know until flush() whether a
// listener claims to have handled the event -- meanwhile, don't block
// other listeners.
return false;
}
void LLEventQueue::flush()
{
if(!mSignal) return;
// Consider the case when a given listener on this LLEventQueue posts yet
// another event on the same queue. If we loop over mEventQueue directly,
// we'll end up processing all those events during the same flush() call
// -- rather like an EventStream. Instead, copy mEventQueue and clear it,
// so that any new events posted to this LLEventQueue during flush() will
// be processed in the *next* flush() call.
EventQueue queue(mEventQueue);
mEventQueue.clear();
// NOTE NOTE NOTE: Any new access to member data beyond this point should
// cause us to move our LLStandardSignal object to a pimpl class along
// with said member data. Then the local shared_ptr will preserve both.
// DEV-43463: capture a local copy of mSignal. See LLEventStream::post()
// for detailed comments.
boost::shared_ptr<LLStandardSignal> signal(mSignal);
for ( ; ! queue.empty(); queue.pop_front())
{
(*signal)(queue.front());
}
mEventHistory.clear();
}
/*****************************************************************************

View File

@ -37,6 +37,7 @@
#include <set>
#include <vector>
#include <deque>
#include <functional>
#if LL_WINDOWS
#pragma warning (push)
#pragma warning (disable : 4263) // boost::signals2::expired_slot::what() has const mismatch
@ -55,7 +56,6 @@
#include <boost/visit_each.hpp>
#include <boost/ref.hpp> // reference_wrapper
#include <boost/type_traits/is_pointer.hpp>
#include <boost/function.hpp>
#include <boost/static_assert.hpp>
#include "llsd.h"
#include "llsingleton.h"
@ -211,8 +211,7 @@ public:
/// exception if you try to call when empty
struct Empty: public LLException
{
Empty(const std::string& what):
LLException(std::string("LLListenerOrPumpName::Empty: ") + what) {}
Empty(const std::string& what): LLException("LLListenerOrPumpName::Empty: " + what) {}
};
private:
@ -247,6 +246,30 @@ public:
*/
LLEventPump& obtain(const std::string& name);
/// exception potentially thrown by make()
struct BadType: public LLException
{
BadType(const std::string& what): LLException("BadType: " + what) {}
};
/**
* Create an LLEventPump with suggested name (optionally of specified
* LLEventPump subclass type). As with obtain(), LLEventPumps owns the new
* instance.
*
* As with LLEventPump's constructor, make() could throw
* LLEventPump::DupPumpName unless you pass tweak=true.
*
* As with a hand-constructed LLEventPump subclass, if you pass
* tweak=true, the tweaked name can be obtained by LLEventPump::getName().
*
* Pass empty type to get the default LLEventStream.
*
* If you pass an unrecognized type string, make() throws BadType.
*/
LLEventPump& make(const std::string& name, bool tweak=false,
const std::string& type=std::string());
/**
* Find the named LLEventPump instance. If it exists post the message to it.
* If the pump does not exist, do nothing.
@ -303,43 +326,21 @@ testable:
// destroyed.
typedef std::set<LLEventPump*> PumpSet;
PumpSet mOurPumps;
// LLEventPump names that should be instantiated as LLEventQueue rather
// than as LLEventStream
typedef std::set<std::string> PumpNames;
PumpNames mQueueNames;
// for make(), map string type name to LLEventPump subclass factory function
typedef std::map<std::string, std::function<LLEventPump*(const std::string&, bool)>> PumpFactories;
// Data used by make().
// One might think mFactories and mTypes could reasonably be static. So
// they could -- if not for the fact that make() or obtain() might be
// called before this module's static variables have been initialized.
// This is why we use singletons in the first place.
PumpFactories mFactories;
// for obtain(), map desired string instance name to string type when
// obtain() must create the instance
typedef std::map<std::string, std::string> InstanceTypes;
InstanceTypes mTypes;
};
/*****************************************************************************
* details
*****************************************************************************/
namespace LLEventDetail
{
/// Any callable capable of connecting an LLEventListener to an
/// LLStandardSignal to produce an LLBoundListener can be mapped to this
/// signature.
typedef boost::function<LLBoundListener(const LLEventListener&)> ConnectFunc;
/// overload of visit_and_connect() when we have a string identifier available
template <typename LISTENER>
LLBoundListener visit_and_connect(const std::string& name,
const LISTENER& listener,
const ConnectFunc& connect_func);
/**
* Utility template function to use Visitor appropriately
*
* @param listener Callable to connect, typically a boost::bind()
* expression. This will be visited by Visitor using boost::visit_each().
* @param connect_func Callable that will connect() @a listener to an
* LLStandardSignal, returning LLBoundListener.
*/
template <typename LISTENER>
LLBoundListener visit_and_connect(const LISTENER& listener,
const ConnectFunc& connect_func)
{
return visit_and_connect("", listener, connect_func);
}
} // namespace LLEventDetail
/*****************************************************************************
* LLEventTrackable
*****************************************************************************/
@ -374,11 +375,6 @@ namespace LLEventDetail
* instance, it attempts to dereference the <tt>Foo*</tt> pointer that was
* <tt>delete</tt>d but not zeroed.)
* - Undefined behavior results.
* If you suspect you may encounter any such scenario, you're better off
* managing the lifespan of your object with <tt>boost::shared_ptr</tt>.
* Passing <tt>LLEventPump::listen()</tt> a <tt>boost::bind()</tt> expression
* involving a <tt>boost::weak_ptr<Foo></tt> is recognized specially, engaging
* thread-safe Boost.Signals2 machinery.
*/
typedef boost::signals2::trackable LLEventTrackable;
@ -387,7 +383,7 @@ typedef boost::signals2::trackable LLEventTrackable;
*****************************************************************************/
/**
* LLEventPump is the base class interface through which we access the
* concrete subclasses LLEventStream and LLEventQueue.
* concrete subclasses such as LLEventStream.
*
* @NOTE
* LLEventPump derives from LLEventTrackable so that when you "chain"
@ -408,8 +404,7 @@ public:
*/
struct DupPumpName: public LLException
{
DupPumpName(const std::string& what):
LLException(std::string("DupPumpName: ") + what) {}
DupPumpName(const std::string& what): LLException("DupPumpName: " + what) {}
};
/**
@ -445,9 +440,7 @@ public:
*/
struct DupListenerName: public ListenError
{
DupListenerName(const std::string& what):
ListenError(std::string("DupListenerName: ") + what)
{}
DupListenerName(const std::string& what): ListenError("DupListenerName: " + what) {}
};
/**
* exception thrown by listen(). The order dependencies specified for your
@ -459,7 +452,7 @@ public:
*/
struct Cycle: public ListenError
{
Cycle(const std::string& what): ListenError(std::string("Cycle: ") + what) {}
Cycle(const std::string& what): ListenError("Cycle: " + what) {}
};
/**
* exception thrown by listen(). This one means that your new listener
@ -480,7 +473,7 @@ public:
*/
struct OrderChange: public ListenError
{
OrderChange(const std::string& what): ListenError(std::string("OrderChange: ") + what) {}
OrderChange(const std::string& what): ListenError("OrderChange: " + what) {}
};
/// used by listen()
@ -517,44 +510,13 @@ public:
* the result be assigned to a LLTempBoundListener or the listener is
* manually disconnected when no longer needed since there will be no
* way to later find and disconnect this listener manually.
*
* If (as is typical) you pass a <tt>boost::bind()</tt> expression as @a
* listener, listen() will inspect the components of that expression. If a
* bound object matches any of several cases, the connection will
* automatically be disconnected when that object is destroyed.
*
* * You bind a <tt>boost::weak_ptr</tt>.
* * Binding a <tt>boost::shared_ptr</tt> that way would ensure that the
* referenced object would @em never be destroyed, since the @c
* shared_ptr stored in the LLEventPump would remain an outstanding
* reference. Use the weaken() function to convert your @c shared_ptr to
* @c weak_ptr. Because this is easy to forget, binding a @c shared_ptr
* will produce a compile error (@c BOOST_STATIC_ASSERT failure).
* * You bind a simple pointer or reference to an object derived from
* <tt>boost::enable_shared_from_this</tt>. (UNDER CONSTRUCTION)
* * You bind a simple pointer or reference to an object derived from
* LLEventTrackable. Unlike the cases described above, though, this is
* vulnerable to a couple of cross-thread race conditions, as described
* in the LLEventTrackable documentation.
*/
template <typename LISTENER>
LLBoundListener listen(const std::string& name, const LISTENER& listener,
LLBoundListener listen(const std::string& name,
const LLEventListener& listener,
const NameList& after=NameList(),
const NameList& before=NameList())
{
// Examine listener, using our listen_impl() method to make the
// actual connection.
// This is why listen() is a template. Conversion from boost::bind()
// to LLEventListener performs type erasure, so it's important to look
// at the boost::bind object itself before that happens.
return LLEventDetail::visit_and_connect(name,
listener,
boost::bind(&LLEventPump::listen_invoke,
this,
name,
_1,
after,
before));
return listen_impl(name, listener, after, before);
}
/// Get the LLBoundListener associated with the passed name (dummy
@ -598,13 +560,6 @@ private:
private:
LLBoundListener listen_invoke(const std::string& name, const LLEventListener& listener,
const NameList& after,
const NameList& before)
{
return this->listen_impl(name, listener, after, before);
}
// must precede mName; see LLEventPump::LLEventPump()
LLHandle<LLEventPumps> mRegistry;
@ -668,11 +623,10 @@ public:
* event *must* eventually reach a listener that will consume it, else the
* queue will grow to arbitrary length.
*
* @NOTE: When using an LLEventMailDrop (or LLEventQueue) with a LLEventTimeout or
* @NOTE: When using an LLEventMailDrop with an LLEventTimeout or
* LLEventFilter attaching the filter downstream, using Timeout's constructor will
* cause the MailDrop to discharge any of its stored events. The timeout should
* instead be connected upstream using its listen() method.
* See llcoro::suspendUntilEventOnWithTimeout() for an example.
*/
class LL_COMMON_API LLEventMailDrop : public LLEventStream
{
@ -684,7 +638,8 @@ public:
virtual bool post(const LLSD& event) override;
/// Remove any history stored in the mail drop.
virtual void flush() override { mEventHistory.clear(); LLEventStream::flush(); };
void discard();
protected:
virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&,
const NameList& after,
@ -695,30 +650,6 @@ private:
EventList mEventHistory;
};
/*****************************************************************************
* LLEventQueue
*****************************************************************************/
/**
* LLEventQueue is a LLEventPump whose post() method defers calling registered
* listeners until flush() is called.
*/
class LL_COMMON_API LLEventQueue: public LLEventPump
{
public:
LLEventQueue(const std::string& name, bool tweak=false): LLEventPump(name, tweak) {}
virtual ~LLEventQueue() {}
/// Post an event to all listeners
virtual bool post(const LLSD& event);
/// flush queued events
virtual void flush();
private:
typedef std::deque<LLSD> EventQueue;
EventQueue mEventQueue;
};
/*****************************************************************************
* LLReqID
*****************************************************************************/
@ -814,329 +745,6 @@ private:
LL_COMMON_API bool sendReply(const LLSD& reply, const LLSD& request,
const std::string& replyKey="reply");
/**
* Base class for LLListenerWrapper. See visit_and_connect() and llwrap(). We
* provide virtual @c accept_xxx() methods, customization points allowing a
* subclass access to certain data visible at LLEventPump::listen() time.
* Example subclass usage:
*
* @code
* myEventPump.listen("somename",
* llwrap<MyListenerWrapper>(boost::bind(&MyClass::method, instance, _1)));
* @endcode
*
* Because of the anticipated usage (note the anonymous temporary
* MyListenerWrapper instance in the example above), the @c accept_xxx()
* methods must be @c const.
*/
class LL_COMMON_API LLListenerWrapperBase
{
public:
/// New instance. The accept_xxx() machinery makes it important to use
/// shared_ptrs for our data. Many copies of this object are made before
/// the instance that actually ends up in the signal, yet accept_xxx()
/// will later be called on the @em original instance. All copies of the
/// same original instance must share the same data.
LLListenerWrapperBase():
mName(new std::string),
mConnection(new LLBoundListener)
{
}
/// Copy constructor. Copy shared_ptrs to original instance data.
LLListenerWrapperBase(const LLListenerWrapperBase& that):
mName(that.mName),
mConnection(that.mConnection)
{
}
virtual ~LLListenerWrapperBase() {}
/// Ask LLEventPump::listen() for the listener name
virtual void accept_name(const std::string& name) const
{
*mName = name;
}
/// Ask LLEventPump::listen() for the new connection
virtual void accept_connection(const LLBoundListener& connection) const
{
*mConnection = connection;
}
protected:
/// Listener name.
boost::shared_ptr<std::string> mName;
/// Connection.
boost::shared_ptr<LLBoundListener> mConnection;
};
/*****************************************************************************
* Underpinnings
*****************************************************************************/
/**
* We originally provided a suite of overloaded
* LLEventTrackable::listenTo(LLEventPump&, ...) methods that would call
* LLEventPump::listen(...) and then pass the returned LLBoundListener to
* LLEventTrackable::track(). This was workable but error-prone: the coder
* must remember to call listenTo() rather than the more straightforward
* listen() method.
*
* Now we publish only the single canonical listen() method, so there's a
* uniform mechanism. Having a single way to do this is good, in that there's
* no question in the coder's mind which of several alternatives to choose.
*
* To support automatic connection management, we use boost::visit_each
* (http://www.boost.org/doc/libs/1_37_0/doc/html/boost/visit_each.html) to
* inspect each argument of a boost::bind expression. (Although the visit_each
* mechanism was first introduced with the original Boost.Signals library, it
* was only later documented.)
*
* Cases:
* * At least one of the function's arguments is a boost::weak_ptr<T>. Pass
* the corresponding shared_ptr to slot_type::track(). Ideally that would be
* the object whose method we want to call, but in fact we do the same for
* any weak_ptr we might find among the bound arguments. If we're passing
* our bound method a weak_ptr to some object, wouldn't the destruction of
* that object invalidate the call? So we disconnect automatically when any
* such object is destroyed. This is the mechanism preferred by boost::
* signals2.
* * One of the functions's arguments is a boost::shared_ptr<T>. This produces
* a compile error: the bound copy of the shared_ptr stored in the
* boost_bind object stored in the signal object would make the referenced
* T object immortal. We provide a weaken() function. Pass
* weaken(your_shared_ptr) instead. (We can inspect, but not modify, the
* boost::bind object. Otherwise we'd replace the shared_ptr with weak_ptr
* implicitly and just proceed.)
* * One of the function's arguments is a plain pointer/reference to an object
* derived from boost::enable_shared_from_this. We assume that this object
* is managed using boost::shared_ptr, so we implicitly extract a shared_ptr
* and track that. (UNDER CONSTRUCTION)
* * One of the function's arguments is derived from LLEventTrackable. Pass
* the LLBoundListener to its LLEventTrackable::track(). This is vulnerable
* to a couple different race conditions, as described in LLEventTrackable
* documentation. (NOTE: Now that LLEventTrackable is a typedef for
* boost::signals2::trackable, the Signals2 library handles this itself, so
* our visitor needs no special logic for this case.)
* * Any other argument type is irrelevant to automatic connection management.
*/
namespace LLEventDetail
{
template <typename F>
const F& unwrap(const F& f) { return f; }
template <typename F>
const F& unwrap(const boost::reference_wrapper<F>& f) { return f.get(); }
// Most of the following is lifted from the Boost.Signals use of
// visit_each.
template<bool Cond> struct truth {};
/**
* boost::visit_each() Visitor, used on a template argument <tt>const F&
* f</tt> as follows (see visit_and_connect()):
* @code
* LLEventListener listener(f);
* Visitor visitor(listener); // bind listener so it can track() shared_ptrs
* using boost::visit_each; // allow unqualified visit_each() call for ADL
* visit_each(visitor, unwrap(f));
* @endcode
*/
class Visitor
{
public:
/**
* Visitor binds a reference to LLEventListener so we can track() any
* shared_ptrs we find in the argument list.
*/
Visitor(LLEventListener& listener):
mListener(listener)
{
}
/**
* boost::visit_each() calls this method for each component of a
* boost::bind() expression.
*/
template <typename T>
void operator()(const T& t) const
{
decode(t, 0);
}
private:
// decode() decides between a reference wrapper and anything else
// boost::ref() variant
template<typename T>
void decode(const boost::reference_wrapper<T>& t, int) const
{
// add_if_trackable(t.get_pointer());
}
// decode() anything else
template<typename T>
void decode(const T& t, long) const
{
typedef truth<(boost::is_pointer<T>::value)> is_a_pointer;
maybe_get_pointer(t, is_a_pointer());
}
// maybe_get_pointer() decides between a pointer and a non-pointer
// plain pointer variant
template<typename T>
void maybe_get_pointer(const T& t, truth<true>) const
{
// add_if_trackable(t);
}
// shared_ptr variant
template<typename T>
void maybe_get_pointer(const boost::shared_ptr<T>& t, truth<false>) const
{
// If we have a shared_ptr to this object, it doesn't matter
// whether the object is derived from LLEventTrackable, so no
// further analysis of T is needed.
// mListener.track(t);
// Make this case illegal. Passing a bound shared_ptr to
// slot_type::track() is useless, since the bound shared_ptr will
// keep the object alive anyway! Force the coder to cast to weak_ptr.
// Trivial as it is, make the BOOST_STATIC_ASSERT() condition
// dependent on template param so the macro is only evaluated if
// this method is in fact instantiated, as described here:
// http://www.boost.org/doc/libs/1_34_1/doc/html/boost_staticassert.html
// ATTENTION: Don't bind a shared_ptr<anything> using
// LLEventPump::listen(boost::bind()). Doing so captures a copy of
// the shared_ptr, making the referenced object effectively
// immortal. Use the weaken() function, e.g.:
// somepump.listen(boost::bind(...weaken(my_shared_ptr)...));
// This lets us automatically disconnect when the referenced
// object is destroyed.
BOOST_STATIC_ASSERT(sizeof(T) == 0);
}
// weak_ptr variant
template<typename T>
void maybe_get_pointer(const boost::weak_ptr<T>& t, truth<false>) const
{
// If we have a weak_ptr to this object, it doesn't matter
// whether the object is derived from LLEventTrackable, so no
// further analysis of T is needed.
mListener.track(t);
// std::cout << "Found weak_ptr<" << typeid(T).name() << ">!\n";
}
#if 0
// reference to anything derived from boost::enable_shared_from_this
template <typename T>
inline void maybe_get_pointer(const boost::enable_shared_from_this<T>& ct,
truth<false>) const
{
// Use the slot_type::track(shared_ptr) mechanism. Cast away
// const-ness because (in our code base anyway) it's unusual
// to find shared_ptr<const T>.
boost::enable_shared_from_this<T>&
t(const_cast<boost::enable_shared_from_this<T>&>(ct));
std::cout << "Capturing shared_from_this()" << std::endl;
boost::shared_ptr<T> sp(t.shared_from_this());
/*==========================================================================*|
std::cout << "Capturing weak_ptr" << std::endl;
boost::weak_ptr<T> wp(sp);
|*==========================================================================*/
std::cout << "Tracking shared__ptr" << std::endl;
mListener.track(sp);
}
#endif
// non-pointer variant
template<typename T>
void maybe_get_pointer(const T& t, truth<false>) const
{
// Take the address of this object, because the object itself may be
// trackable
// add_if_trackable(boost::addressof(t));
}
/*==========================================================================*|
// add_if_trackable() adds LLEventTrackable objects to mTrackables
inline void add_if_trackable(const LLEventTrackable* t) const
{
if (t)
{
}
}
// pointer to anything not an LLEventTrackable subclass
inline void add_if_trackable(const void*) const
{
}
// pointer to free function
// The following construct uses the preprocessor to generate
// add_if_trackable() overloads accepting pointer-to-function taking
// 0, 1, ..., LLEVENTS_LISTENER_ARITY parameters of arbitrary type.
#define BOOST_PP_LOCAL_MACRO(n) \
template <typename R \
BOOST_PP_COMMA_IF(n) \
BOOST_PP_ENUM_PARAMS(n, typename T)> \
inline void \
add_if_trackable(R (*)(BOOST_PP_ENUM_PARAMS(n, T))) const \
{ \
}
#define BOOST_PP_LOCAL_LIMITS (0, LLEVENTS_LISTENER_ARITY)
#include BOOST_PP_LOCAL_ITERATE()
#undef BOOST_PP_LOCAL_MACRO
#undef BOOST_PP_LOCAL_LIMITS
|*==========================================================================*/
/// Bind a reference to the LLEventListener to call its track() method.
LLEventListener& mListener;
};
/**
* Utility template function to use Visitor appropriately
*
* @param raw_listener Callable to connect, typically a boost::bind()
* expression. This will be visited by Visitor using boost::visit_each().
* @param connect_funct Callable that will connect() @a raw_listener to an
* LLStandardSignal, returning LLBoundListener.
*/
template <typename LISTENER>
LLBoundListener visit_and_connect(const std::string& name,
const LISTENER& raw_listener,
const ConnectFunc& connect_func)
{
// Capture the listener
LLEventListener listener(raw_listener);
// Define our Visitor, binding the listener so we can call
// listener.track() if we discover any shared_ptr<Foo>.
LLEventDetail::Visitor visitor(listener);
// Allow unqualified visit_each() call for ADL
using boost::visit_each;
// Visit each component of a boost::bind() expression. Pass
// 'raw_listener', our template argument, rather than 'listener' from
// which type details have been erased. unwrap() comes from
// Boost.Signals, in case we were passed a boost::ref().
visit_each(visitor, LLEventDetail::unwrap(raw_listener));
// Make the connection using passed function.
LLBoundListener connection(connect_func(listener));
// If the LISTENER is an LLListenerWrapperBase subclass, pass it the
// desired information. It's important that we pass the raw_listener
// so the compiler can make decisions based on its original type.
const LLListenerWrapperBase* lwb =
ll_template_cast<const LLListenerWrapperBase*>(&raw_listener);
if (lwb)
{
lwb->accept_name(name);
lwb->accept_connection(connection);
}
// In any case, show new connection to caller.
return connection;
}
} // namespace LLEventDetail
// Somewhat to my surprise, passing boost::bind(...boost::weak_ptr<T>...) to
// listen() fails in Boost code trying to instantiate LLEventListener (i.e.
// LLStandardSignal::slot_type) because the boost::get_pointer() utility function isn't
@ -1147,12 +755,4 @@ namespace boost
T* get_pointer(const weak_ptr<T>& ptr) { return shared_ptr<T>(ptr).get(); }
}
/// Since we forbid use of listen(boost::bind(...shared_ptr<T>...)), provide an
/// easy way to cast to the corresponding weak_ptr.
template <typename T>
boost::weak_ptr<T> weaken(const boost::shared_ptr<T>& ptr)
{
return boost::weak_ptr<T>(ptr);
}
#endif /* ! defined(LL_LLEVENTS_H) */

View File

@ -40,16 +40,83 @@ public:
LLEventTimer(F32 period); // period is the amount of time between each call to tick() in seconds
LLEventTimer(const LLDate& time);
virtual ~LLEventTimer();
//function to be called at the supplied frequency
// Normally return FALSE; TRUE will delete the timer after the function returns.
virtual BOOL tick() = 0;
static void updateClass();
/// Schedule recurring calls to generic callable every period seconds.
/// Returns a pointer; if you delete it, cancels the recurring calls.
template <typename CALLABLE>
static LLEventTimer* run_every(F32 period, const CALLABLE& callable);
/// Schedule a future call to generic callable. Returns a pointer.
/// CAUTION: The object referenced by that pointer WILL BE DELETED once
/// the callback has been called! LLEventTimer::getInstance(pointer) (NOT
/// pointer->getInstance(pointer)!) can be used to test whether the
/// pointer is still valid. If it is, deleting it will cancel the
/// callback.
template <typename CALLABLE>
static LLEventTimer* run_at(const LLDate& time, const CALLABLE& callable);
/// Like run_at(), but after a time delta rather than at a timestamp.
/// Same CAUTION.
template <typename CALLABLE>
static LLEventTimer* run_after(F32 interval, const CALLABLE& callable);
protected:
LLTimer mEventTimer;
F32 mPeriod;
private:
template <typename CALLABLE>
class Generic;
};
template <typename CALLABLE>
class LLEventTimer::Generic: public LLEventTimer
{
public:
// making TIME generic allows engaging either LLEventTimer constructor
template <typename TIME>
Generic(const TIME& time, bool once, const CALLABLE& callable):
LLEventTimer(time),
mOnce(once),
mCallable(callable)
{}
BOOL tick() override
{
mCallable();
// true tells updateClass() to delete this instance
return mOnce;
}
private:
bool mOnce;
CALLABLE mCallable;
};
template <typename CALLABLE>
LLEventTimer* LLEventTimer::run_every(F32 period, const CALLABLE& callable)
{
// return false to schedule recurring calls
return new Generic<CALLABLE>(period, false, callable);
}
template <typename CALLABLE>
LLEventTimer* LLEventTimer::run_at(const LLDate& time, const CALLABLE& callable)
{
// return true for one-shot callback
return new Generic<CALLABLE>(time, true, callable);
}
template <typename CALLABLE>
LLEventTimer* LLEventTimer::run_after(F32 interval, const CALLABLE& callable)
{
// one-shot callback after specified interval
return new Generic<CALLABLE>(interval, true, callable);
}
#endif //LL_EVENTTIMER_H

View File

@ -86,6 +86,69 @@ public:
static const char * tmpdir();
};
/// RAII class
class LLUniqueFile
{
public:
// empty
LLUniqueFile(): mFileHandle(nullptr) {}
// wrap (e.g.) result of LLFile::fopen()
LLUniqueFile(LLFILE* f): mFileHandle(f) {}
// no copy
LLUniqueFile(const LLUniqueFile&) = delete;
// move construction
LLUniqueFile(LLUniqueFile&& other)
{
mFileHandle = other.mFileHandle;
other.mFileHandle = nullptr;
}
// The point of LLUniqueFile is to close on destruction.
~LLUniqueFile()
{
close();
}
// simple assignment
LLUniqueFile& operator=(LLFILE* f)
{
close();
mFileHandle = f;
return *this;
}
// copy assignment deleted
LLUniqueFile& operator=(const LLUniqueFile&) = delete;
// move assignment
LLUniqueFile& operator=(LLUniqueFile&& other)
{
close();
std::swap(mFileHandle, other.mFileHandle);
return *this;
}
// explicit close operation
void close()
{
if (mFileHandle)
{
// in case close() throws, set mFileHandle null FIRST
LLFILE* h{nullptr};
std::swap(h, mFileHandle);
LLFile::close(h);
}
}
// detect whether the wrapped LLFILE is open or not
explicit operator bool() const { return bool(mFileHandle); }
bool operator!() { return ! mFileHandle; }
// LLUniqueFile should be usable for any operation that accepts LLFILE*
// (or FILE* for that matter)
operator LLFILE*() const { return mFileHandle; }
private:
LLFILE* mFileHandle;
};
#if LL_WINDOWS
/**
* @brief Controlling input for files.

View File

@ -267,6 +267,9 @@ public:
return instance_iter(getMap_().end());
}
// while iterating over instances, might want to request the key
virtual const KEY& getKey() const { return mInstanceKey; }
static S32 instanceCount()
{
return getMap_().size();
@ -301,7 +304,6 @@ protected:
remove_();
}
virtual void setKey(KEY key) { remove_(); add_(key); }
virtual const KEY& getKey() const { return mInstanceKey; }
private:
LLInstanceTracker( const LLInstanceTracker& );

View File

@ -14,6 +14,8 @@
// associated header
#include "llleaplistener.h"
// STL headers
#include <map>
#include <functional>
// std headers
// external library headers
#include <boost/foreach.hpp>
@ -60,16 +62,11 @@ LLLeapListener::LLLeapListener(const ConnectFunc& connect):
LLSD need_name(LLSDMap("name", LLSD()));
add("newpump",
"Instantiate a new LLEventPump named like [\"name\"] and listen to it.\n"
"If [\"type\"] == \"LLEventQueue\", make LLEventQueue, else LLEventStream.\n"
"[\"type\"] == \"LLEventStream\", \"LLEventMailDrop\" et al.\n"
"Events sent through new LLEventPump will be decorated with [\"pump\"]=name.\n"
"Returns actual name in [\"name\"] (may be different if collision).",
&LLLeapListener::newpump,
need_name);
add("killpump",
"Delete LLEventPump [\"name\"] created by \"newpump\".\n"
"Returns [\"status\"] boolean indicating whether such a pump existed.",
&LLLeapListener::killpump,
need_name);
LLSD need_source_listener(LLSDMap("source", LLSD())("listener", LLSD()));
add("listen",
"Listen to an existing LLEventPump named [\"source\"], with listener name\n"
@ -124,40 +121,23 @@ void LLLeapListener::newpump(const LLSD& request)
Response reply(LLSD(), request);
std::string name = request["name"];
LLSD const & type = request["type"];
std::string type = request["type"];
LLEventPump * new_pump = NULL;
if (type.asString() == "LLEventQueue")
try
{
new_pump = new LLEventQueue(name, true); // tweak name for uniqueness
// tweak name for uniqueness
LLEventPump& new_pump(LLEventPumps::instance().make(name, true, type));
name = new_pump.getName();
reply["name"] = name;
// Now listen on this new pump with our plugin listener
std::string myname("llleap");
saveListener(name, myname, mConnect(new_pump, myname));
}
else
catch (const LLEventPumps::BadType& error)
{
if (! (type.isUndefined() || type.asString() == "LLEventStream"))
{
reply.warn(STRINGIZE("unknown 'type' " << type << ", using LLEventStream"));
}
new_pump = new LLEventStream(name, true); // tweak name for uniqueness
reply.error(error.what());
}
name = new_pump->getName();
mEventPumps.insert(name, new_pump);
// Now listen on this new pump with our plugin listener
std::string myname("llleap");
saveListener(name, myname, mConnect(*new_pump, myname));
reply["name"] = name;
}
void LLLeapListener::killpump(const LLSD& request)
{
Response reply(LLSD(), request);
std::string name = request["name"];
// success == (nonzero number of entries were erased)
reply["status"] = bool(mEventPumps.erase(name));
}
void LLLeapListener::listen(const LLSD& request)

View File

@ -40,7 +40,6 @@ public:
private:
void newpump(const LLSD&);
void killpump(const LLSD&);
void listen(const LLSD&);
void stoplistening(const LLSD&);
void ping(const LLSD&) const;
@ -64,10 +63,6 @@ private:
// and listener name.
typedef std::map<std::pair<std::string, std::string>, LLBoundListener> ListenersMap;
ListenersMap mListeners;
// Similar lifespan reasoning applies to LLEventPumps instantiated by
// newpump() operations.
typedef boost::ptr_map<std::string, LLEventPump> EventPumpsMap;
EventPumpsMap mEventPumps;
};
#endif /* ! defined(LL_LLLEAPLISTENER_H) */

View File

@ -1,198 +0,0 @@
/**
* @file lllistenerwrapper.h
* @author Nat Goodspeed
* @date 2009-11-30
* @brief Introduce LLListenerWrapper template
*
* $LicenseInfo:firstyear=2009&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#if ! defined(LL_LLLISTENERWRAPPER_H)
#define LL_LLLISTENERWRAPPER_H
#include "llevents.h" // LLListenerWrapperBase
#include <boost/visit_each.hpp>
/**
* Template base class for coding wrappers for LLEventPump listeners.
*
* Derive your listener wrapper from LLListenerWrapper. You must use
* LLLISTENER_WRAPPER_SUBCLASS() so your subclass will play nicely with
* boost::visit_each (q.v.). That way boost::signals2 can still detect
* derivation from LLEventTrackable, and so forth.
*/
template <typename LISTENER>
class LLListenerWrapper: public LLListenerWrapperBase
{
public:
/// Wrap an arbitrary listener object
LLListenerWrapper(const LISTENER& listener):
mListener(listener)
{}
/// call
virtual bool operator()(const LLSD& event)
{
return mListener(event);
}
/// Allow boost::visit_each() to peek at our mListener.
template <class V>
void accept_visitor(V& visitor) const
{
using boost::visit_each;
visit_each(visitor, mListener, 0);
}
private:
LISTENER mListener;
};
/**
* Specialize boost::visit_each() (leveraging ADL) to peek inside an
* LLListenerWrapper<T> to traverse its LISTENER. We borrow the
* accept_visitor() pattern from boost::bind(), avoiding the need to make
* mListener public.
*/
template <class V, typename T>
void visit_each(V& visitor, const LLListenerWrapper<T>& wrapper, int)
{
wrapper.accept_visitor(visitor);
}
/// use this (sigh!) for each subclass of LLListenerWrapper<T> you write
#define LLLISTENER_WRAPPER_SUBCLASS(CLASS) \
template <class V, typename T> \
void visit_each(V& visitor, const CLASS<T>& wrapper, int) \
{ \
visit_each(visitor, static_cast<const LLListenerWrapper<T>&>(wrapper), 0); \
} \
\
/* Have to state this explicitly, rather than using LL_TEMPLATE_CONVERTIBLE, */ \
/* because the source type is itself a template. */ \
template <typename T> \
struct ll_template_cast_impl<const LLListenerWrapperBase*, const CLASS<T>*> \
{ \
const LLListenerWrapperBase* operator()(const CLASS<T>* wrapper) \
{ \
return wrapper; \
} \
}
/**
* Make an instance of a listener wrapper. Every wrapper class must be a
* template accepting a listener object of arbitrary type. In particular, the
* type of a boost::bind() expression is deliberately undocumented. So we
* can't just write Wrapper<CorrectType>(boost::bind(...)). Instead we must
* write llwrap<Wrapper>(boost::bind(...)).
*/
template <template<typename> class WRAPPER, typename T>
WRAPPER<T> llwrap(const T& listener)
{
return WRAPPER<T>(listener);
}
/**
* This LLListenerWrapper template subclass is used to report entry/exit to an
* event listener, by changing this:
* @code
* someEventPump.listen("MyClass",
* boost::bind(&MyClass::method, ptr, _1));
* @endcode
* to this:
* @code
* someEventPump.listen("MyClass",
* llwrap<LLCoutListener>(
* boost::bind(&MyClass::method, ptr, _1)));
* @endcode
*/
template <class LISTENER>
class LLCoutListener: public LLListenerWrapper<LISTENER>
{
typedef LLListenerWrapper<LISTENER> super;
public:
/// Wrap an arbitrary listener object
LLCoutListener(const LISTENER& listener):
super(listener)
{}
/// call
virtual bool operator()(const LLSD& event)
{
std::cout << "Entering listener " << *super::mName << " with " << event << std::endl;
bool handled = super::operator()(event);
std::cout << "Leaving listener " << *super::mName;
if (handled)
{
std::cout << " (handled)";
}
std::cout << std::endl;
return handled;
}
};
LLLISTENER_WRAPPER_SUBCLASS(LLCoutListener);
/**
* This LLListenerWrapper template subclass is used to log entry/exit to an
* event listener, by changing this:
* @code
* someEventPump.listen("MyClass",
* boost::bind(&MyClass::method, ptr, _1));
* @endcode
* to this:
* @code
* someEventPump.listen("MyClass",
* llwrap<LLLogListener>(
* boost::bind(&MyClass::method, ptr, _1)));
* @endcode
*/
template <class LISTENER>
class LLLogListener: public LLListenerWrapper<LISTENER>
{
typedef LLListenerWrapper<LISTENER> super;
public:
/// Wrap an arbitrary listener object
LLLogListener(const LISTENER& listener):
super(listener)
{}
/// call
virtual bool operator()(const LLSD& event)
{
LL_DEBUGS("LLLogListener") << "Entering listener " << *super::mName << " with " << event << LL_ENDL;
bool handled = super::operator()(event);
LL_DEBUGS("LLLogListener") << "Leaving listener " << *super::mName;
if (handled)
{
LL_CONT << " (handled)";
}
LL_CONT << LL_ENDL;
return handled;
}
};
LLLISTENER_WRAPPER_SUBCLASS(LLLogListener);
#endif /* ! defined(LL_LLLISTENERWRAPPER_H) */

View File

@ -25,37 +25,10 @@
#if ! defined(LL_LLMAKE_H)
#define LL_LLMAKE_H
/*==========================================================================*|
// When we allow ourselves to compile with C++11 features enabled, this form
// should generically handle an arbitrary number of arguments.
template <template<typename...> class CLASS_TEMPLATE, typename... ARGS>
CLASS_TEMPLATE<ARGS...> llmake(ARGS && ... args)
{
return CLASS_TEMPLATE<ARGS...>(std::forward<ARGS>(args)...);
}
|*==========================================================================*/
// As of 2015-12-18, this is what we'll use instead. Add explicit overloads
// for different numbers of template parameters as use cases arise.
/**
* Usage: llmake<SomeTemplate>(arg)
*
* Deduces the type T of 'arg' and returns an instance of SomeTemplate<T>
* initialized with 'arg'. Assumes a constructor accepting T (by value,
* reference or whatever).
*/
template <template<typename> class CLASS_TEMPLATE, typename ARG1>
CLASS_TEMPLATE<ARG1> llmake(const ARG1& arg1)
{
return CLASS_TEMPLATE<ARG1>(arg1);
}
template <template<typename, typename> class CLASS_TEMPLATE, typename ARG1, typename ARG2>
CLASS_TEMPLATE<ARG1, ARG2> llmake(const ARG1& arg1, const ARG2& arg2)
{
return CLASS_TEMPLATE<ARG1, ARG2>(arg1, arg2);
}
#endif /* ! defined(LL_LLMAKE_H) */

View File

@ -129,6 +129,16 @@ LL_COMMON_API std::string llsd_matches(const LLSD& prototype, const LLSD& data,
/// equality rather than bitwise equality, pass @a bits as for
/// is_approx_equal_fraction().
LL_COMMON_API bool llsd_equals(const LLSD& lhs, const LLSD& rhs, int bits=-1);
/// If you don't care about LLSD::Real equality
inline bool operator==(const LLSD& lhs, const LLSD& rhs)
{
return llsd_equals(lhs, rhs);
}
inline bool operator!=(const LLSD& lhs, const LLSD& rhs)
{
// operator!=() should always be the negation of operator==()
return ! (lhs == rhs);
}
// Simple function to copy data out of input & output iterators if
// there is no need for casting.
@ -211,6 +221,36 @@ private:
LLSD _data;
};
namespace llsd
{
/**
* Construct an LLSD::Array inline, using modern C++ variadic arguments.
*/
// recursion tail
inline
void array_(LLSD&) {}
// recursive call
template <typename T0, typename... Ts>
void array_(LLSD& data, T0&& v0, Ts&&... vs)
{
data.append(std::forward<T0>(v0));
array_(data, std::forward<Ts>(vs)...);
}
// public interface
template <typename... Ts>
LLSD array(Ts&&... vs)
{
LLSD data;
array_(data, std::forward<Ts>(vs)...);
return data;
}
} // namespace llsd
/*****************************************************************************
* LLSDMap
*****************************************************************************/
@ -255,6 +295,36 @@ private:
LLSD _data;
};
namespace llsd
{
/**
* Construct an LLSD::Map inline, using modern C++ variadic arguments.
*/
// recursion tail
inline
void map_(LLSD&) {}
// recursive call
template <typename T0, typename... Ts>
void map_(LLSD& data, const LLSD::String& k0, T0&& v0, Ts&&... vs)
{
data[k0] = v0;
map_(data, std::forward<Ts>(vs)...);
}
// public interface
template <typename... Ts>
LLSD map(Ts&&... vs)
{
LLSD data;
map_(data, std::forward<Ts>(vs)...);
return data;
}
} // namespace llsd
/*****************************************************************************
* LLSDParam
*****************************************************************************/

View File

@ -33,7 +33,7 @@
#include <sstream>
#include "llwin32headerslean.h"
#pragma warning (push)
#pragma warning (push)
#pragma warning (disable:4091) // a microsoft header has warnings. Very nice.
#include <dbghelp.h>
#pragma warning (pop)

View File

@ -23,7 +23,7 @@
* $/LicenseInfo$
*/
//#include "linden_common.h"
//#include "llthreadsafequeue.h"
#include "linden_common.h"
#include "llthreadsafequeue.h"

View File

@ -37,12 +37,15 @@
#include <iostream>
#include <string>
#include <typeinfo>
#include "../test/lltut.h"
#include "../test/lltestapp.h"
#include "llsd.h"
#include "llsdutil.h"
#include "llevents.h"
#include "llcoros.h"
#include "lleventfilter.h"
#include "lleventcoro.h"
#include "../test/debug.h"
#include "../test/sync.h"
@ -96,6 +99,7 @@ namespace tut
std::string replyName, errorName, threw, stringdata;
LLSD result, errordata;
int which;
LLTestApp testApp;
void explicit_wait(boost::shared_ptr<LLCoros::Promise<std::string>>& cbp);
void waitForEventOn1();
@ -255,4 +259,80 @@ namespace tut
LLCoros::instance().launch("test<5>", [this](){ coroPumpPost(); });
ensure_equals(result.asInteger(), 18);
}
template <class PUMP>
void test()
{
PUMP pump(typeid(PUMP).name());
bool running{false};
LLSD data{LLSD::emptyArray()};
// start things off by posting once before even starting the listener
// coro
LL_DEBUGS() << "test() posting first" << LL_ENDL;
LLSD first{LLSDMap("desc", "first")("value", 0)};
bool consumed = pump.post(first);
ensure("should not have consumed first", ! consumed);
// now launch the coro
LL_DEBUGS() << "test() launching listener coro" << LL_ENDL;
running = true;
LLCoros::instance().launch(
"listener",
[&pump, &running, &data](){
// important for this test that we consume posted values
LLCoros::instance().set_consuming(true);
// should immediately retrieve 'first' without waiting
LL_DEBUGS() << "listener coro waiting for first" << LL_ENDL;
data.append(llcoro::suspendUntilEventOnWithTimeout(pump, 0.1, LLSD()));
// Don't use ensure() from within the coro -- ensure() failure
// throws tut::fail, which won't propagate out to the main
// test driver, which will result in an odd failure.
// Wait for 'second' because it's not already pending.
LL_DEBUGS() << "listener coro waiting for second" << LL_ENDL;
data.append(llcoro::suspendUntilEventOnWithTimeout(pump, 0.1, LLSD()));
// and wait for 'third', which should involve no further waiting
LL_DEBUGS() << "listener coro waiting for third" << LL_ENDL;
data.append(llcoro::suspendUntilEventOnWithTimeout(pump, 0.1, LLSD()));
LL_DEBUGS() << "listener coro done" << LL_ENDL;
running = false;
});
// back from coro at the point where it's waiting for 'second'
LL_DEBUGS() << "test() posting second" << LL_ENDL;
LLSD second{llsd::map("desc", "second", "value", 1)};
consumed = pump.post(second);
ensure("should have consumed second", consumed);
// This is a key point: even though we've post()ed the value for which
// the coroutine is waiting, it's actually still suspended until we
// pause for some other reason. The coroutine will only pick up one
// value at a time from our 'pump'. It's important to exercise the
// case when we post() two values before it picks up either.
LL_DEBUGS() << "test() posting third" << LL_ENDL;
LLSD third{llsd::map("desc", "third", "value", 2)};
consumed = pump.post(third);
ensure("should NOT yet have consumed third", ! consumed);
// now just wait for coro to finish -- which it eventually will, given
// that all its suspend calls have short timeouts.
while (running)
{
LL_DEBUGS() << "test() waiting for coro done" << LL_ENDL;
llcoro::suspendUntilTimeout(0.1);
}
// okay, verify expected results
ensure_equals("should have received three values", data,
llsd::array(first, second, third));
LL_DEBUGS() << "test() done" << LL_ENDL;
}
template<> template<>
void object::test<6>()
{
set_test_name("LLEventMailDrop");
tut::test<LLEventMailDrop>();
}
template<> template<>
void object::test<7>()
{
set_test_name("LLEventLogProxyFor<LLEventMailDrop>");
tut::test< LLEventLogProxyFor<LLEventMailDrop> >();
}
}

View File

@ -36,9 +36,12 @@
// other Linden headers
#include "../test/lltut.h"
#include "stringize.h"
#include "llsdutil.h"
#include "listener.h"
#include "tests/wrapllerrs.h"
#include <typeinfo>
/*****************************************************************************
* Test classes
*****************************************************************************/
@ -407,6 +410,78 @@ namespace tut
throttle.post(";17");
ensure_equals("17", cat.result, "136;12;17"); // "17" delivered
}
template<class PUMP>
void test()
{
PUMP pump(typeid(PUMP).name());
LLSD data{LLSD::emptyArray()};
bool consumed{true};
// listener that appends to 'data'
// but that also returns the current value of 'consumed'
// Instantiate this separately because we're going to listen()
// multiple times with the same lambda: LLEventMailDrop only replays
// queued events on a new listen() call.
auto lambda =
[&data, &consumed](const LLSD& event)->bool
{
data.append(event);
return consumed;
};
{
LLTempBoundListener conn = pump.listen("lambda", lambda);
pump.post("first");
}
// first post() should certainly be received by listener
ensure_equals("first", data, llsd::array("first"));
// the question is, since consumed was true, did it queue the value?
data = LLSD::emptyArray();
{
// if it queued the value, it would be delivered on subsequent
// listen() call
LLTempBoundListener conn = pump.listen("lambda", lambda);
}
ensure_equals("empty1", data, LLSD::emptyArray());
data = LLSD::emptyArray();
// now let's NOT consume the posted data
consumed = false;
{
LLTempBoundListener conn = pump.listen("lambda", lambda);
pump.post("second");
pump.post("third");
}
// the two events still arrive
ensure_equals("second,third1", data, llsd::array("second", "third"));
data = LLSD::emptyArray();
{
// when we reconnect, these should be delivered again
// but this time they should be consumed
consumed = true;
LLTempBoundListener conn = pump.listen("lambda", lambda);
}
// unconsumed events were delivered again
ensure_equals("second,third2", data, llsd::array("second", "third"));
data = LLSD::emptyArray();
{
// when we reconnect this time, no more unconsumed events
LLTempBoundListener conn = pump.listen("lambda", lambda);
}
ensure_equals("empty2", data, LLSD::emptyArray());
}
template<> template<>
void filter_object::test<6>()
{
set_test_name("LLEventMailDrop");
tut::test<LLEventMailDrop>();
}
template<> template<>
void filter_object::test<7>()
{
set_test_name("LLEventLogProxyFor<LLEventMailDrop>");
tut::test< LLEventLogProxyFor<LLEventMailDrop> >();
}
} // namespace tut
/*****************************************************************************

View File

@ -94,21 +94,27 @@ private:
// we use a buffered_channel here rather than unbuffered_channel since we want to be able to
// push values without blocking,even if there's currently no one calling a pop operation (due to
// fibber running right now)
// fiber running right now)
typedef boost::fibers::buffered_channel<QueuedCoproc::ptr_t> CoprocQueue_t;
// Use shared_ptr to control the lifespan of our CoprocQueue_t instance
// because the consuming coroutine might outlive this LLCoprocedurePool
// instance.
typedef boost::shared_ptr<CoprocQueue_t> CoprocQueuePtr;
typedef std::map<LLUUID, LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t> ActiveCoproc_t;
std::string mPoolName;
size_t mPoolSize;
CoprocQueue_t mPendingCoprocs;
CoprocQueuePtr mPendingCoprocs;
ActiveCoproc_t mActiveCoprocs;
LLTempBoundListener mStatusListener;
typedef std::map<std::string, LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t> CoroAdapterMap_t;
LLCore::HttpRequest::policy_t mHTTPPolicy;
CoroAdapterMap_t mCoroMapping;
void coprocedureInvokerCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter);
void coprocedureInvokerCoro(CoprocQueuePtr pendingCoprocs,
LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter);
};
//=========================================================================
@ -149,7 +155,7 @@ LLCoprocedureManager::poolPtr_t LLCoprocedureManager::initializePool(const std::
mPropertyDefineFn(keyName, size, "Coroutine Pool size for " + poolName);
}
LL_WARNS() << "LLCoprocedureManager: No setting for \"" << keyName << "\" setting pool size to default of " << size << LL_ENDL;
LL_WARNS("CoProcMgr") << "LLCoprocedureManager: No setting for \"" << keyName << "\" setting pool size to default of " << size << LL_ENDL;
}
poolPtr_t pool(new LLCoprocedurePool(poolName, size));
@ -213,21 +219,42 @@ void LLCoprocedureManager::close(const std::string &pool)
LLCoprocedurePool::LLCoprocedurePool(const std::string &poolName, size_t size):
mPoolName(poolName),
mPoolSize(size),
mPendingCoprocs(DEFAULT_QUEUE_SIZE),
mCoroMapping(),
mHTTPPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID)
mPendingCoprocs(boost::make_shared<CoprocQueue_t>(DEFAULT_QUEUE_SIZE)),
mHTTPPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID),
mCoroMapping()
{
// store in our LLTempBoundListener so that when the LLCoprocedurePool is
// destroyed, we implicitly disconnect from this LLEventPump
mStatusListener = LLEventPumps::instance().obtain("LLApp").listen(
poolName,
[pendingCoprocs=mPendingCoprocs, poolName](const LLSD& status)
{
auto& statsd = status["status"];
if (statsd.asString() != "running")
{
LL_INFOS("CoProcMgr") << "Pool " << poolName
<< " closing queue because status " << statsd
<< LL_ENDL;
// This should ensure that all waiting coprocedures in this
// pool will wake up and terminate.
pendingCoprocs->close();
}
return false;
});
for (size_t count = 0; count < mPoolSize; ++count)
{
LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter( mPoolName + "Adapter", mHTTPPolicy));
std::string pooledCoro = LLCoros::instance().launch("LLCoprocedurePool("+mPoolName+")::coprocedureInvokerCoro",
boost::bind(&LLCoprocedurePool::coprocedureInvokerCoro, this, httpAdapter));
std::string pooledCoro = LLCoros::instance().launch(
"LLCoprocedurePool("+mPoolName+")::coprocedureInvokerCoro",
boost::bind(&LLCoprocedurePool::coprocedureInvokerCoro, this,
mPendingCoprocs, httpAdapter));
mCoroMapping.insert(CoroAdapterMap_t::value_type(pooledCoro, httpAdapter));
}
LL_INFOS() << "Created coprocedure pool named \"" << mPoolName << "\" with " << size << " items." << LL_ENDL;
LL_INFOS("CoProcMgr") << "Created coprocedure pool named \"" << mPoolName << "\" with " << size << " items." << LL_ENDL;
}
LLCoprocedurePool::~LLCoprocedurePool()
@ -239,19 +266,29 @@ LLUUID LLCoprocedurePool::enqueueCoprocedure(const std::string &name, LLCoproced
{
LLUUID id(LLUUID::generateNewID());
mPendingCoprocs.push(QueuedCoproc::ptr_t(new QueuedCoproc(name, id, proc)));
LL_INFOS() << "Coprocedure(" << name << ") enqueued with id=" << id.asString() << " in pool \"" << mPoolName << "\"" << LL_ENDL;
mPendingCoprocs->push(QueuedCoproc::ptr_t(new QueuedCoproc(name, id, proc)));
LL_INFOS("CoProcMgr") << "Coprocedure(" << name << ") enqueued with id=" << id.asString() << " in pool \"" << mPoolName << "\"" << LL_ENDL;
return id;
}
//-------------------------------------------------------------------------
void LLCoprocedurePool::coprocedureInvokerCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter)
void LLCoprocedurePool::coprocedureInvokerCoro(
CoprocQueuePtr pendingCoprocs,
LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter)
{
QueuedCoproc::ptr_t coproc;
boost::fibers::channel_op_status status;
while ((status = mPendingCoprocs.pop_wait_for(coproc, std::chrono::seconds(10))) != boost::fibers::channel_op_status::closed)
for (;;)
{
{
LLCoros::TempStatus st("waiting for work for 10s");
status = pendingCoprocs->pop_wait_for(coproc, std::chrono::seconds(10));
}
if (status == boost::fibers::channel_op_status::closed)
{
break;
}
if(status == boost::fibers::channel_op_status::timeout)
{
LL_INFOS_ONCE() << "pool '" << mPoolName << "' stalled." << LL_ENDL;
@ -261,7 +298,7 @@ void LLCoprocedurePool::coprocedureInvokerCoro(LLCoreHttpUtil::HttpCoroutineAdap
ActiveCoproc_t::iterator itActive = mActiveCoprocs.insert(ActiveCoproc_t::value_type(coproc->mId, httpAdapter)).first;
// Nicky: This is super spammy. Consider using LL_DEBUGS here?
LL_INFOS() << "Dequeued and invoking coprocedure(" << coproc->mName << ") with id=" << coproc->mId.asString() << " in pool \"" << mPoolName << "\"" << LL_ENDL;
LL_DEBUGS("CoProcMgr") << "Dequeued and invoking coprocedure(" << coproc->mName << ") with id=" << coproc->mId.asString() << " in pool \"" << mPoolName << "\"" << LL_ENDL;
try
{
@ -278,7 +315,7 @@ void LLCoprocedurePool::coprocedureInvokerCoro(LLCoreHttpUtil::HttpCoroutineAdap
}
// Nicky: This is super spammy. Consider using LL_DEBUGS here?
LL_INFOS() << "Finished coprocedure(" << coproc->mName << ")" << " in pool \"" << mPoolName << "\"" << LL_ENDL;
LL_DEBUGS("CoProcMgr") << "Finished coprocedure(" << coproc->mName << ")" << " in pool \"" << mPoolName << "\"" << LL_ENDL;
mActiveCoprocs.erase(itActive);
}
@ -286,5 +323,5 @@ void LLCoprocedurePool::coprocedureInvokerCoro(LLCoreHttpUtil::HttpCoroutineAdap
void LLCoprocedurePool::close()
{
mPendingCoprocs.close();
mPendingCoprocs->close();
}

View File

@ -32,6 +32,7 @@
#include "llcoros.h"
#include "llcorehttputil.h"
#include "lluuid.h"
#include <boost/smart_ptr/shared_ptr.hpp>
class LLCoprocedurePool;

View File

@ -750,42 +750,24 @@ public:
virtual ~LLNotificationChannelBase() {}
// you can also connect to a Channel, so you can be notified of
// changes to this channel
template <typename LISTENER>
LLBoundListener connectChanged(const LISTENER& slot)
LLBoundListener connectChanged(const LLEventListener& slot)
{
// Examine slot to see if it binds an LLEventTrackable subclass, or a
// boost::shared_ptr to something, or a boost::weak_ptr to something.
// Call this->connectChangedImpl() to actually connect it.
return LLEventDetail::visit_and_connect(slot,
boost::bind(&LLNotificationChannelBase::connectChangedImpl,
this,
_1));
return connectChangedImpl(slot);
}
template <typename LISTENER>
LLBoundListener connectAtFrontChanged(const LISTENER& slot)
LLBoundListener connectAtFrontChanged(const LLEventListener& slot)
{
return LLEventDetail::visit_and_connect(slot,
boost::bind(&LLNotificationChannelBase::connectAtFrontChangedImpl,
this,
_1));
return connectAtFrontChangedImpl(slot);
}
template <typename LISTENER>
LLBoundListener connectPassedFilter(const LISTENER& slot)
LLBoundListener connectPassedFilter(const LLEventListener& slot)
{
// see comments in connectChanged()
return LLEventDetail::visit_and_connect(slot,
boost::bind(&LLNotificationChannelBase::connectPassedFilterImpl,
this,
_1));
return connectPassedFilterImpl(slot);
}
template <typename LISTENER>
LLBoundListener connectFailedFilter(const LISTENER& slot)
LLBoundListener connectFailedFilter(const LLEventListener& slot)
{
// see comments in connectChanged()
return LLEventDetail::visit_and_connect(slot,
boost::bind(&LLNotificationChannelBase::connectFailedFilterImpl,
this,
_1));
return connectFailedFilterImpl(slot);
}
// use this when items change or to add a new one

View File

@ -5481,6 +5481,9 @@ void LLAppViewer::idle()
LLFrameTimer::updateFrameTime();
LLFrameTimer::updateFrameCount();
LLEventTimer::updateClass();
// LLApp::stepFrame() performs the above three calls plus mRunner.run().
// Not sure why we don't call stepFrame() here, except that LLRunner seems
// completely redundant with LLEventTimer.
LLNotificationsUI::LLToast::updateClass();
LLSmoothInterpolation::updateInterpolants();
LLMortician::updateClass();

View File

@ -68,6 +68,43 @@ F32 LLViewerJoystick::sDelta[] = {0,0,0,0,0,0,0};
#define MAX_SPACENAVIGATOR_INPUT 3000.0f
#define MAX_JOYSTICK_INPUT_VALUE MAX_SPACENAVIGATOR_INPUT
#if LIB_NDOF
std::ostream& operator<<(std::ostream& out, NDOF_Device* ptr)
{
if (! ptr)
{
return out << "nullptr";
}
out << "NDOF_Device{ ";
out << "axes [";
const char* delim = "";
for (short axis = 0; axis < ptr->axes_count; ++axis)
{
out << delim << ptr->axes[axis];
delim = ", ";
}
out << "]";
out << ", buttons [";
delim = "";
for (short button = 0; button < ptr->btn_count; ++button)
{
out << delim << ptr->buttons[button];
delim = ", ";
}
out << "]";
out << ", range " << ptr->axes_min << ':' << ptr->axes_max;
// If we don't coerce these to unsigned, they're streamed as characters,
// e.g. ctrl-A or nul.
out << ", absolute " << unsigned(ptr->absolute);
out << ", valid " << unsigned(ptr->valid);
out << ", manufacturer '" << ptr->manufacturer << "'";
out << ", product '" << ptr->product << "'";
out << ", private " << ptr->private_data;
out << " }";
return out;
}
#endif // LIB_NDOF
// -----------------------------------------------------------------------------
void LLViewerJoystick::updateEnabled(bool autoenable)
{
@ -113,11 +150,11 @@ NDOF_HotPlugResult LLViewerJoystick::HotPlugAddCallback(NDOF_Device *dev)
LLViewerJoystick* joystick(LLViewerJoystick::getInstance());
if (joystick->mDriverState == JDS_UNINITIALIZED)
{
LL_INFOS() << "HotPlugAddCallback: will use device:" << LL_ENDL;
ndof_dump(dev);
LL_INFOS("joystick") << "HotPlugAddCallback: will use device:" << LL_ENDL;
ndof_dump(stderr, dev);
joystick->mNdofDev = dev;
joystick->mDriverState = JDS_INITIALIZED;
res = NDOF_KEEP_HOTPLUGGED;
joystick->mDriverState = JDS_INITIALIZED;
res = NDOF_KEEP_HOTPLUGGED;
}
joystick->updateEnabled(true);
return res;
@ -131,9 +168,9 @@ void LLViewerJoystick::HotPlugRemovalCallback(NDOF_Device *dev)
LLViewerJoystick* joystick(LLViewerJoystick::getInstance());
if (joystick->mNdofDev == dev)
{
LL_INFOS() << "HotPlugRemovalCallback: joystick->mNdofDev="
LL_INFOS("joystick") << "HotPlugRemovalCallback: joystick->mNdofDev="
<< joystick->mNdofDev << "; removed device:" << LL_ENDL;
ndof_dump(dev);
ndof_dump(stderr, dev);
joystick->mDriverState = JDS_UNINITIALIZED;
}
joystick->updateEnabled(true);
@ -199,6 +236,7 @@ void LLViewerJoystick::init(bool autoenable)
{
if (mNdofDev)
{
LL_DEBUGS("joystick") << "ndof_create() returned: " << mNdofDev << LL_ENDL;
// Different joysticks will return different ranges of raw values.
// Since we want to handle every device in the same uniform way,
// we initialize the mNdofDev struct and we set the range
@ -217,16 +255,19 @@ void LLViewerJoystick::init(bool autoenable)
// just have the absolute values instead.
mNdofDev->absolute = 1;
LL_DEBUGS("joystick") << "ndof_init_first() received: " << mNdofDev << LL_ENDL;
// init & use the first suitable NDOF device found on the USB chain
if (ndof_init_first(mNdofDev, NULL))
{
mDriverState = JDS_UNINITIALIZED;
LL_WARNS() << "ndof_init_first FAILED" << LL_ENDL;
LL_WARNS("joystick") << "ndof_init_first FAILED" << LL_ENDL;
ndof_dump_list(stderr);
}
else
{
mDriverState = JDS_INITIALIZED;
}
LL_DEBUGS("joystick") << "ndof_init_first() left: " << mNdofDev << LL_ENDL;
}
else
{
@ -264,8 +305,8 @@ void LLViewerJoystick::init(bool autoenable)
{
// No device connected, don't change any settings
}
LL_INFOS() << "ndof: mDriverState=" << mDriverState << "; mNdofDev="
LL_INFOS("joystick") << "ndof: mDriverState=" << mDriverState << "; mNdofDev="
<< mNdofDev << "; libinit=" << libinit << LL_ENDL;
#endif
}
@ -276,7 +317,7 @@ void LLViewerJoystick::terminate()
#if LIB_NDOF
ndof_libcleanup();
LL_INFOS() << "Terminated connection with NDOF device." << LL_ENDL;
LL_INFOS("joystick") << "Terminated connection with NDOF device." << LL_ENDL;
mDriverState = JDS_UNINITIALIZED;
#endif
}
@ -1089,7 +1130,7 @@ std::string LLViewerJoystick::getDescription()
bool LLViewerJoystick::isLikeSpaceNavigator() const
{
#if LIB_NDOF
#if LIB_NDOF
return (isJoystickInitialized()
&& (strncmp(mNdofDev->product, "SpaceNavigator", 14) == 0
|| strncmp(mNdofDev->product, "SpaceExplorer", 13) == 0
@ -1113,10 +1154,10 @@ void LLViewerJoystick::setSNDefaults()
const float platformScaleAvXZ = 2.f;
const bool is_3d_cursor = true;
#endif
//gViewerWindow->alertXml("CacheWillClear");
LL_INFOS() << "restoring SpaceNavigator defaults..." << LL_ENDL;
LL_INFOS("joystick") << "restoring SpaceNavigator defaults..." << LL_ENDL;
gSavedSettings.setS32("JoystickAxis0", 1); // z (at)
gSavedSettings.setS32("JoystickAxis1", 0); // x (slide)
gSavedSettings.setS32("JoystickAxis2", 2); // y (up)
@ -1124,11 +1165,11 @@ void LLViewerJoystick::setSNDefaults()
gSavedSettings.setS32("JoystickAxis4", 3); // roll
gSavedSettings.setS32("JoystickAxis5", 5); // yaw
gSavedSettings.setS32("JoystickAxis6", -1);
gSavedSettings.setBOOL("Cursor3D", is_3d_cursor);
gSavedSettings.setBOOL("AutoLeveling", true);
gSavedSettings.setBOOL("ZoomDirect", false);
gSavedSettings.setF32("AvatarAxisScale0", 1.f * platformScaleAvXZ);
gSavedSettings.setF32("AvatarAxisScale1", 1.f * platformScaleAvXZ);
gSavedSettings.setF32("AvatarAxisScale2", 1.f);

View File

@ -391,7 +391,7 @@ void LLVivoxVoiceClient::init(LLPumpIO *pump)
// constructor will set up LLVoiceClient::getInstance()
LLVivoxVoiceClient::getInstance()->mPump = pump;
// LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro();",
// LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro",
// boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance()));
}
@ -1156,8 +1156,6 @@ bool LLVivoxVoiceClient::provisionVoiceAccount()
bool LLVivoxVoiceClient::establishVoiceConnection()
{
LLEventPump &voiceConnectPump = LLEventPumps::instance().obtain("vivoxClientPump");
if (!mVoiceEnabled && mIsInitialized)
{
LL_WARNS("Voice") << "cannot establish connection; enabled "<<mVoiceEnabled<<" initialized "<<mIsInitialized<<LL_ENDL;
@ -1174,7 +1172,7 @@ bool LLVivoxVoiceClient::establishVoiceConnection()
connectorCreate();
do
{
result = llcoro::suspendUntilEventOn(voiceConnectPump);
result = llcoro::suspendUntilEventOn(mVivoxPump);
LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
if (result.has("connector"))
@ -1226,7 +1224,6 @@ bool LLVivoxVoiceClient::establishVoiceConnection()
bool LLVivoxVoiceClient::breakVoiceConnection(bool corowait)
{
LL_DEBUGS("Voice") << "( wait=" << corowait << ")" << LL_ENDL;
LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump");
bool retval(true);
mShutdownComplete = false;
@ -1236,7 +1233,7 @@ bool LLVivoxVoiceClient::breakVoiceConnection(bool corowait)
{
LLSD timeoutResult(LLSDMap("connector", "timeout"));
LLSD result = llcoro::suspendUntilEventOnWithTimeout(voicePump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult);
LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult);
LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
retval = result.has("connector");
@ -1254,22 +1251,13 @@ bool LLVivoxVoiceClient::breakVoiceConnection(bool corowait)
// the message, yet we need to receive "connector shutdown response".
// Either wait a bit and emulate it or check gMessageSystem for specific message
_sleep(1000);
// <FS:Ansariel> Cut down wait on logout
//if (mConnected)
//{
// mConnected = false;
// LLSD vivoxevent(LLSDMap("connector", LLSD::Boolean(false)));
// LLEventPumps::instance().post("vivoxClientPump", vivoxevent);
//}
//mShutdownComplete = true;
// Need to check messages on the service pump for the connector shutdown response
// which sets mShutdownComplete to true!
while (gMessageSystem->checkAllMessages(gFrameCount, gServicePump))
if (mConnected)
{
// Do nothing - just check messages
mConnected = false;
LLSD vivoxevent(LLSDMap("connector", LLSD::Boolean(false)));
mVivoxPump.post(vivoxevent);
}
gMessageSystem->processAcks();
// </FS:Ansariel>
mShutdownComplete = true;
}
#endif
}
@ -1284,8 +1272,6 @@ bool LLVivoxVoiceClient::breakVoiceConnection(bool corowait)
bool LLVivoxVoiceClient::loginToVivox()
{
LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump");
LLSD timeoutResult(LLSDMap("login", "timeout"));
int loginRetryCount(0);
@ -1303,7 +1289,7 @@ bool LLVivoxVoiceClient::loginToVivox()
send_login = false;
}
LLSD result = llcoro::suspendUntilEventOnWithTimeout(voicePump, LOGIN_ATTEMPT_TIMEOUT, timeoutResult);
LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGIN_ATTEMPT_TIMEOUT, timeoutResult);
LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
if (result.has("login"))
@ -1386,15 +1372,14 @@ void LLVivoxVoiceClient::logoutOfVivox(bool wait)
if (wait)
{
LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump");
LLSD timeoutResult(LLSDMap("logout", "timeout"));
LL_DEBUGS("Voice")
<< "waiting for logout response on "
<< voicePump.getName()
<< mVivoxPump.getName()
<< LL_ENDL;
LLSD result = llcoro::suspendUntilEventOnWithTimeout(voicePump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult);
LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult);
LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
}
@ -1410,8 +1395,6 @@ void LLVivoxVoiceClient::logoutOfVivox(bool wait)
bool LLVivoxVoiceClient::retrieveVoiceFonts()
{
LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump");
// Request the set of available voice fonts.
refreshVoiceEffectLists(true);
@ -1419,7 +1402,7 @@ bool LLVivoxVoiceClient::retrieveVoiceFonts()
LLSD result;
do
{
result = llcoro::suspendUntilEventOn(voicePump);
result = llcoro::suspendUntilEventOn(mVivoxPump);
LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
if (result.has("voice_fonts"))
@ -1535,7 +1518,6 @@ bool LLVivoxVoiceClient::requestParcelVoiceInfo()
bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession)
{
LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump");
mIsJoiningSession = true;
sessionStatePtr_t oldSession = mAudioSession;
@ -1624,7 +1606,7 @@ bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession)
// We are about to start a whole new session. Anything that MIGHT still be in our
// maildrop is going to be stale and cause us much wailing and gnashing of teeth.
// Just flush it all out and start new.
voicePump.flush();
mVivoxPump.discard();
// It appears that I need to wait for BOTH the SessionGroup.AddSession response and the SessionStateChangeEvent with state 4
// before continuing from this state. They can happen in either order, and if I don't wait for both, things can get stuck.
@ -1632,7 +1614,7 @@ bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession)
// This is a cheap way to make sure both have happened before proceeding.
do
{
result = llcoro::suspendUntilEventOnWithTimeout(voicePump, SESSION_JOIN_TIMEOUT, timeoutResult);
result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, SESSION_JOIN_TIMEOUT, timeoutResult);
LL_INFOS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
if (result.has("session"))
@ -1746,13 +1728,12 @@ bool LLVivoxVoiceClient::terminateAudioSession(bool wait)
if (wait)
{
LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump");
LLSD result;
do
{
LLSD timeoutResult(LLSDMap("session", "timeout"));
result = llcoro::suspendUntilEventOnWithTimeout(voicePump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult);
result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult);
LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
if (result.has("session"))
@ -1954,7 +1935,6 @@ bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session)
LLSD timeoutEvent(LLSDMap("timeout", LLSD::Boolean(true)));
LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump");
mIsInChannel = true;
mMuteMicDirty = true;
@ -2006,7 +1986,7 @@ bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session)
sendLocalAudioUpdates();
mIsInitialized = true;
LLSD result = llcoro::suspendUntilEventOnWithTimeout(voicePump, UPDATE_THROTTLE_SECONDS, timeoutEvent);
LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, UPDATE_THROTTLE_SECONDS, timeoutEvent);
if (!result.has("timeout")) // logging the timeout event spams the log
{
LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
@ -2077,14 +2057,13 @@ void LLVivoxVoiceClient::sendCaptureAndRenderDevices()
void LLVivoxVoiceClient::recordingAndPlaybackMode()
{
LL_INFOS("Voice") << "In voice capture/playback mode." << LL_ENDL;
LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump");
while (true)
{
LLSD command;
do
{
command = llcoro::suspendUntilEventOn(voicePump);
command = llcoro::suspendUntilEventOn(mVivoxPump);
LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(command) << LL_ENDL;
} while (!command.has("recplay"));
@ -2117,7 +2096,6 @@ int LLVivoxVoiceClient::voiceRecordBuffer()
LL_INFOS("Voice") << "Recording voice buffer" << LL_ENDL;
LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump");
LLSD result;
captureBufferRecordStartSendMessage();
@ -2125,7 +2103,7 @@ int LLVivoxVoiceClient::voiceRecordBuffer()
do
{
result = llcoro::suspendUntilEventOnWithTimeout(voicePump, CAPTURE_BUFFER_MAX_TIME, timeoutResult);
result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, CAPTURE_BUFFER_MAX_TIME, timeoutResult);
LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
} while (!result.has("recplay"));
@ -2147,7 +2125,6 @@ int LLVivoxVoiceClient::voicePlaybackBuffer()
LL_INFOS("Voice") << "Playing voice buffer" << LL_ENDL;
LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump");
LLSD result;
do
@ -2162,7 +2139,7 @@ int LLVivoxVoiceClient::voicePlaybackBuffer()
// Update UI, should really use a separate callback.
notifyVoiceFontObservers();
result = llcoro::suspendUntilEventOnWithTimeout(voicePump, CAPTURE_BUFFER_MAX_TIME, timeoutResult);
result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, CAPTURE_BUFFER_MAX_TIME, timeoutResult);
LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
} while (!result.has("recplay"));
@ -2689,7 +2666,7 @@ void LLVivoxVoiceClient::tuningStart()
mTuningMode = true;
if (!mIsCoroutineActive)
{
LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro();",
LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro",
boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance()));
}
else if (mIsInChannel)
@ -3363,7 +3340,7 @@ void LLVivoxVoiceClient::connectorCreateResponse(int statusCode, std::string &st
result["connector"] = LLSD::Boolean(false);
}
LLEventPumps::instance().post("vivoxClientPump", result);
mVivoxPump.post(result);
}
void LLVivoxVoiceClient::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases)
@ -3398,7 +3375,7 @@ void LLVivoxVoiceClient::loginResponse(int statusCode, std::string &statusString
result["login"] = LLSD::String("response_ok");
}
LLEventPumps::instance().post("vivoxClientPump", result);
mVivoxPump.post(result);
}
@ -3424,7 +3401,7 @@ void LLVivoxVoiceClient::sessionCreateResponse(std::string &requestId, int statu
("session", "failed")
("reason", LLSD::Integer(statusCode)));
LLEventPumps::instance().post("vivoxClientPump", vivoxevent);
mVivoxPump.post(vivoxevent);
}
else
{
@ -3442,7 +3419,7 @@ void LLVivoxVoiceClient::sessionCreateResponse(std::string &requestId, int statu
LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle))
("session", "created"));
LLEventPumps::instance().post("vivoxClientPump", vivoxevent);
mVivoxPump.post(vivoxevent);
}
}
@ -3467,7 +3444,7 @@ void LLVivoxVoiceClient::sessionGroupAddSessionResponse(std::string &requestId,
LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle))
("session", "failed"));
LLEventPumps::instance().post("vivoxClientPump", vivoxevent);
mVivoxPump.post(vivoxevent);
}
else
{
@ -3486,7 +3463,7 @@ void LLVivoxVoiceClient::sessionGroupAddSessionResponse(std::string &requestId,
LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle))
("session", "added"));
LLEventPumps::instance().post("vivoxClientPump", vivoxevent);
mVivoxPump.post(vivoxevent);
}
}
@ -3529,7 +3506,7 @@ void LLVivoxVoiceClient::logoutResponse(int statusCode, std::string &statusStrin
}
LLSD vivoxevent(LLSDMap("logout", LLSD::Boolean(true)));
LLEventPumps::instance().post("vivoxClientPump", vivoxevent);
mVivoxPump.post(vivoxevent);
}
void LLVivoxVoiceClient::connectorShutdownResponse(int statusCode, std::string &statusString)
@ -3545,7 +3522,7 @@ void LLVivoxVoiceClient::connectorShutdownResponse(int statusCode, std::string &
LLSD vivoxevent(LLSDMap("connector", LLSD::Boolean(false)));
LLEventPumps::instance().post("vivoxClientPump", vivoxevent);
mVivoxPump.post(vivoxevent);
}
void LLVivoxVoiceClient::sessionAddedEvent(
@ -3654,7 +3631,7 @@ void LLVivoxVoiceClient::joinedAudioSession(const sessionStatePtr_t &session)
LLSD vivoxevent(LLSDMap("handle", LLSD::String(session->mHandle))
("session", "joined"));
LLEventPumps::instance().post("vivoxClientPump", vivoxevent);
mVivoxPump.post(vivoxevent);
// Add the current user as a participant here.
participantStatePtr_t participant(session->addParticipant(sipURIFromName(mAccountName)));
@ -3798,7 +3775,7 @@ void LLVivoxVoiceClient::leftAudioSession(const sessionStatePtr_t &session)
LLSD vivoxevent(LLSDMap("handle", LLSD::String(session->mHandle))
("session", "removed"));
LLEventPumps::instance().post("vivoxClientPump", vivoxevent);
mVivoxPump.post(vivoxevent);
}
}
@ -3826,7 +3803,7 @@ void LLVivoxVoiceClient::accountLoginStateChangeEvent(
case 1:
levent["login"] = LLSD::String("account_login");
LLEventPumps::instance().post("vivoxClientPump", levent);
mVivoxPump.post(levent);
break;
case 2:
break;
@ -3834,7 +3811,7 @@ void LLVivoxVoiceClient::accountLoginStateChangeEvent(
case 3:
levent["login"] = LLSD::String("account_loggingOut");
LLEventPumps::instance().post("vivoxClientPump", levent);
mVivoxPump.post(levent);
break;
case 4:
@ -3847,7 +3824,7 @@ void LLVivoxVoiceClient::accountLoginStateChangeEvent(
case 0:
levent["login"] = LLSD::String("account_logout");
LLEventPumps::instance().post("vivoxClientPump", levent);
mVivoxPump.post(levent);
break;
default:
@ -3882,7 +3859,7 @@ void LLVivoxVoiceClient::mediaCompletionEvent(std::string &sessionGroupHandle, s
}
if (!result.isUndefined())
LLEventPumps::instance().post("vivoxClientPump", result);
mVivoxPump.post(result);
}
void LLVivoxVoiceClient::mediaStreamUpdatedEvent(
@ -5305,7 +5282,7 @@ void LLVivoxVoiceClient::setVoiceEnabled(bool enabled)
if (!mIsCoroutineActive)
{
LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro();",
LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro",
boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance()));
}
else
@ -6716,7 +6693,7 @@ void LLVivoxVoiceClient::accountGetSessionFontsResponse(int statusCode, const st
// receiving the last one.
LLSD result(LLSDMap("voice_fonts", LLSD::Boolean(true)));
LLEventPumps::instance().post("vivoxClientPump", result);
mVivoxPump.post(result);
}
notifyVoiceFontObservers();
mVoiceFontsReceived = true;
@ -6867,7 +6844,7 @@ void LLVivoxVoiceClient::enablePreviewBuffer(bool enable)
else
result["recplay"] = "quit";
LLEventPumps::instance().post("vivoxClientPump", result);
mVivoxPump.post(result);
if(mCaptureBufferMode && mIsInChannel)
{
@ -6888,7 +6865,7 @@ void LLVivoxVoiceClient::recordPreviewBuffer()
mCaptureBufferRecording = true;
LLSD result(LLSDMap("recplay", "record"));
LLEventPumps::instance().post("vivoxClientPump", result);
mVivoxPump.post(result);
}
void LLVivoxVoiceClient::playPreviewBuffer(const LLUUID& effect_id)
@ -6911,7 +6888,7 @@ void LLVivoxVoiceClient::playPreviewBuffer(const LLUUID& effect_id)
mCaptureBufferPlaying = true;
LLSD result(LLSDMap("recplay", "playback"));
LLEventPumps::instance().post("vivoxClientPump", result);
mVivoxPump.post(result);
}
void LLVivoxVoiceClient::stopPreviewBuffer()
@ -6920,7 +6897,7 @@ void LLVivoxVoiceClient::stopPreviewBuffer()
mCaptureBufferPlaying = false;
LLSD result(LLSDMap("recplay", "quit"));
LLEventPumps::instance().post("vivoxClientPump", result);
mVivoxPump.post(result);
}
bool LLVivoxVoiceClient::isPreviewRecording()

View File

@ -91,7 +91,11 @@ void LLWatchdogEntry::start()
void LLWatchdogEntry::stop()
{
LLWatchdog::getInstance()->remove(this);
// this can happen very late in the shutdown sequence
if (! LLWatchdog::wasDeleted())
{
LLWatchdog::getInstance()->remove(this);
}
}
// LLWatchdogTimeout

View File

@ -29,8 +29,8 @@
#include "stdtypes.h"
#include "llwin32headerslean.h"
#pragma warning (push)
#pragma warning (push)
#pragma warning (disable:4091) // a microsoft header has warnings. Very nice.
#include <dbghelp.h>
#pragma warning (pop)

View File

@ -1782,8 +1782,6 @@ class DarwinManifest(ViewerManifest):
print "Converting temp disk image to final disk image"
self.run_command(['hdiutil', 'convert', sparsename, '-format', 'UDZO',
'-imagekey', 'zlib-level=9', '-o', finalname])
#<FS:TS> This command no longer exists as of OS X Catalina
#self.run_command(['hdiutil', 'internet-enable', '-yes', finalname])
# get rid of the temp file
self.package_file = finalname
self.remove(sparsename)

View File

@ -38,7 +38,6 @@
#define testable public
#include "llevents.h"
#undef testable
#include "lllistenerwrapper.h"
// STL headers
// std headers
#include <iostream>
@ -92,9 +91,7 @@ template<> template<>
void events_object::test<1>()
{
set_test_name("basic operations");
// Now there's a static constructor in llevents.cpp that registers on
// the "mainloop" pump to call LLEventPumps::flush().
// Actually -- having to modify this to track the statically-
// Having to modify this to track the statically-
// constructed pumps in other TUT modules in this giant monolithic test
// executable isn't such a hot idea.
// ensure_equals("initial pump", pumps.mPumpMap.size(), 1);
@ -212,43 +209,6 @@ bool chainEvents(Listener& someListener, const LLSD& event)
template<> template<>
void events_object::test<3>()
{
set_test_name("LLEventQueue delayed action");
// This access is NOT legal usage: we can do it only because we're
// hacking private for test purposes. Normally we'd either compile in
// a particular name, or (later) edit a config file.
pumps.mQueueNames.insert("login");
LLEventPump& login(pumps.obtain("login"));
// The "mainloop" pump is special: posting on that implicitly calls
// LLEventPumps::flush(), which in turn should flush our "login"
// LLEventQueue.
LLEventPump& mainloop(pumps.obtain("mainloop"));
ensure("LLEventQueue leaf class", dynamic_cast<LLEventQueue*> (&login));
listener0.listenTo(login);
listener0.reset(0);
login.post(1);
check_listener("waiting for queued event", listener0, 0);
mainloop.post(LLSD());
check_listener("got queued event", listener0, 1);
login.stopListening(listener0.getName());
// Verify that when an event handler posts a new event on the same
// LLEventQueue, it doesn't get processed in the same flush() call --
// it waits until the next flush() call.
listener0.reset(17);
login.listen("chainEvents", boost::bind(chainEvents, boost::ref(listener0), _1));
login.post(1);
check_listener("chainEvents(1) not yet called", listener0, 17);
mainloop.post(LLSD());
check_listener("chainEvents(1) called", listener0, 1);
mainloop.post(LLSD());
check_listener("chainEvents(0) called", listener0, 0);
mainloop.post(LLSD());
check_listener("chainEvents(-1) not called", listener0, 0);
login.stopListening("chainEvents");
}
template<> template<>
void events_object::test<4>()
{
set_test_name("explicitly-instantiated LLEventStream");
// Explicitly instantiate an LLEventStream, and verify that it
@ -273,7 +233,7 @@ void events_object::test<4>()
}
template<> template<>
void events_object::test<5>()
void events_object::test<4>()
{
set_test_name("stopListening()");
LLEventPump& login(pumps.obtain("login"));
@ -287,7 +247,7 @@ void events_object::test<5>()
}
template<> template<>
void events_object::test<6>()
void events_object::test<5>()
{
set_test_name("chaining LLEventPump instances");
LLEventPump& upstream(pumps.obtain("upstream"));
@ -312,7 +272,7 @@ void events_object::test<6>()
}
template<> template<>
void events_object::test<7>()
void events_object::test<6>()
{
set_test_name("listener dependency order");
typedef LLEventPump::NameList NameList;
@ -398,7 +358,7 @@ void events_object::test<7>()
}
template<> template<>
void events_object::test<8>()
void events_object::test<7>()
{
set_test_name("tweaked and untweaked LLEventPump instance names");
{ // nested scope
@ -432,7 +392,7 @@ void eventSource(const LLListenerOrPumpName& listener)
}
template<> template<>
void events_object::test<9>()
void events_object::test<8>()
{
set_test_name("LLListenerOrPumpName");
// Passing a boost::bind() expression to LLListenerOrPumpName
@ -475,7 +435,7 @@ private:
};
template<> template<>
void events_object::test<10>()
void events_object::test<9>()
{
set_test_name("listen(boost::bind(...TempListener...))");
// listen() can't do anything about a plain TempListener instance:
@ -503,223 +463,60 @@ void events_object::test<10>()
heaptest.stopListening("temp");
}
template<> template<>
void events_object::test<11>()
{
set_test_name("listen(boost::bind(...weak_ptr...))");
// listen() detecting weak_ptr<TempListener> in boost::bind() object
bool live = false;
LLEventPump& heaptest(pumps.obtain("heaptest"));
LLBoundListener connection;
ensure("default state", !connection.connected());
{
boost::shared_ptr<TempListener> newListener(new TempListener("heap", live));
newListener->reset();
ensure("TempListener constructed", live);
connection = heaptest.listen(newListener->getName(),
boost::bind(&Listener::call,
weaken(newListener),
_1));
ensure("new connection", connection.connected());
heaptest.post(1);
check_listener("received", *newListener, 1);
} // presumably this will make newListener go away?
// verify that
ensure("TempListener destroyed", !live);
ensure("implicit disconnect", !connection.connected());
// now just make sure we don't blow up trying to access a freed object!
heaptest.post(2);
}
template<> template<>
void events_object::test<12>()
{
set_test_name("listen(boost::bind(...shared_ptr...))");
/*==========================================================================*|
// DISABLED because I've made this case produce a compile error.
// Following the error leads the disappointed dev to a comment
// instructing her to use the weaken() function to bind a weak_ptr<T>
// instead of binding a shared_ptr<T>, and explaining why. I know of
// no way to use TUT to code a repeatable test in which the expected
// outcome is a compile error. The interested reader is invited to
// uncomment this block and build to see for herself.
// listen() detecting shared_ptr<TempListener> in boost::bind() object
bool live = false;
LLEventPump& heaptest(pumps.obtain("heaptest"));
LLBoundListener connection;
std::string listenerName("heap");
ensure("default state", !connection.connected());
{
boost::shared_ptr<TempListener> newListener(new TempListener(listenerName, live));
ensure_equals("use_count", newListener.use_count(), 1);
newListener->reset();
ensure("TempListener constructed", live);
connection = heaptest.listen(newListener->getName(),
boost::bind(&Listener::call, newListener, _1));
ensure("new connection", connection.connected());
ensure_equals("use_count", newListener.use_count(), 2);
heaptest.post(1);
check_listener("received", *newListener, 1);
} // this should make newListener go away...
// Unfortunately, the fact that we've bound a shared_ptr by value into
// our LLEventPump means that copy will keep the referenced object alive.
ensure("TempListener still alive", live);
ensure("still connected", connection.connected());
// disconnecting explicitly should delete the TempListener...
heaptest.stopListening(listenerName);
#if 0 // however, in my experience, it does not. I don't know why not.
// Ah: on 2009-02-19, Frank Mori Hess, author of the Boost.Signals2
// library, stated on the boost-users mailing list:
// http://www.nabble.com/Re%3A--signals2--review--The-review-of-the-signals2-library-(formerly-thread_safe_signals)-begins-today%2C-Nov-1st-p22102367.html
// "It will get destroyed eventually. The signal cleans up its slot
// list little by little during connect/invoke. It doesn't immediately
// remove disconnected slots from the slot list since other threads
// might be using the same slot list concurrently. It might be
// possible to make it immediately reset the shared_ptr owning the
// slot though, leaving an empty shared_ptr in the slot list, since
// that wouldn't invalidate any iterators."
ensure("TempListener destroyed", ! live);
ensure("implicit disconnect", ! connection.connected());
#endif // 0
// now just make sure we don't blow up trying to access a freed object!
heaptest.post(2);
|*==========================================================================*/
}
class TempTrackableListener: public TempListener, public LLEventTrackable
{
public:
TempTrackableListener(const std::string& name, bool& liveFlag):
TempListener(name, liveFlag)
{}
TempTrackableListener(const std::string& name, bool& liveFlag):
TempListener(name, liveFlag)
{}
};
template<> template<>
void events_object::test<13>()
void events_object::test<10>()
{
set_test_name("listen(boost::bind(...TempTrackableListener ref...))");
bool live = false;
LLEventPump& heaptest(pumps.obtain("heaptest"));
LLBoundListener connection;
{
TempTrackableListener tempListener("temp", live);
ensure("TempTrackableListener constructed", live);
connection = heaptest.listen(tempListener.getName(),
boost::bind(&TempTrackableListener::call,
boost::ref(tempListener), _1));
heaptest.post(1);
check_listener("received", tempListener, 1);
} // presumably this will make tempListener go away?
// verify that
ensure("TempTrackableListener destroyed", ! live);
ensure("implicit disconnect", ! connection.connected());
// now just make sure we don't blow up trying to access a freed object!
heaptest.post(2);
set_test_name("listen(boost::bind(...TempTrackableListener ref...))");
bool live = false;
LLEventPump& heaptest(pumps.obtain("heaptest"));
LLBoundListener connection;
{
TempTrackableListener tempListener("temp", live);
ensure("TempTrackableListener constructed", live);
connection = heaptest.listen(tempListener.getName(),
boost::bind(&TempTrackableListener::call,
boost::ref(tempListener), _1));
heaptest.post(1);
check_listener("received", tempListener, 1);
} // presumably this will make tempListener go away?
// verify that
ensure("TempTrackableListener destroyed", ! live);
ensure("implicit disconnect", ! connection.connected());
// now just make sure we don't blow up trying to access a freed object!
heaptest.post(2);
}
template<> template<>
void events_object::test<14>()
void events_object::test<11>()
{
set_test_name("listen(boost::bind(...TempTrackableListener pointer...))");
bool live = false;
LLEventPump& heaptest(pumps.obtain("heaptest"));
LLBoundListener connection;
{
TempTrackableListener* newListener(new TempTrackableListener("temp", live));
ensure("TempTrackableListener constructed", live);
connection = heaptest.listen(newListener->getName(),
boost::bind(&TempTrackableListener::call,
newListener, _1));
heaptest.post(1);
check_listener("received", *newListener, 1);
// explicitly destroy newListener
delete newListener;
}
// verify that
ensure("TempTrackableListener destroyed", ! live);
ensure("implicit disconnect", ! connection.connected());
// now just make sure we don't blow up trying to access a freed object!
heaptest.post(2);
set_test_name("listen(boost::bind(...TempTrackableListener pointer...))");
bool live = false;
LLEventPump& heaptest(pumps.obtain("heaptest"));
LLBoundListener connection;
{
TempTrackableListener* newListener(new TempTrackableListener("temp", live));
ensure("TempTrackableListener constructed", live);
connection = heaptest.listen(newListener->getName(),
boost::bind(&TempTrackableListener::call,
newListener, _1));
heaptest.post(1);
check_listener("received", *newListener, 1);
// explicitly destroy newListener
delete newListener;
}
// verify that
ensure("TempTrackableListener destroyed", ! live);
ensure("implicit disconnect", ! connection.connected());
// now just make sure we don't blow up trying to access a freed object!
heaptest.post(2);
}
template<> template<>
void events_object::test<15>()
{
// This test ensures that using an LLListenerWrapper subclass doesn't
// block Boost.Signals2 from recognizing a bound LLEventTrackable
// subclass.
set_test_name("listen(llwrap<LLLogListener>(boost::bind(...TempTrackableListener ref...)))");
bool live = false;
LLEventPump& heaptest(pumps.obtain("heaptest"));
LLBoundListener connection;
{
TempTrackableListener tempListener("temp", live);
ensure("TempTrackableListener constructed", live);
connection = heaptest.listen(tempListener.getName(),
llwrap<LLLogListener>(
boost::bind(&TempTrackableListener::call,
boost::ref(tempListener), _1)));
heaptest.post(1);
check_listener("received", tempListener, 1);
} // presumably this will make tempListener go away?
// verify that
ensure("TempTrackableListener destroyed", ! live);
ensure("implicit disconnect", ! connection.connected());
// now just make sure we don't blow up trying to access a freed object!
heaptest.post(2);
}
class TempSharedListener: public TempListener,
public boost::enable_shared_from_this<TempSharedListener>
{
public:
TempSharedListener(const std::string& name, bool& liveFlag):
TempListener(name, liveFlag)
{}
};
template<> template<>
void events_object::test<16>()
{
set_test_name("listen(boost::bind(...TempSharedListener ref...))");
#if 0
bool live = false;
LLEventPump& heaptest(pumps.obtain("heaptest"));
LLBoundListener connection;
{
// We MUST have at least one shared_ptr to an
// enable_shared_from_this subclass object before
// shared_from_this() can work.
boost::shared_ptr<TempSharedListener>
tempListener(new TempSharedListener("temp", live));
ensure("TempSharedListener constructed", live);
// However, we're not passing either the shared_ptr or its
// corresponding weak_ptr -- instead, we're passing a reference to
// the TempSharedListener.
/*==========================================================================*|
std::cout << "Capturing const ref" << std::endl;
const boost::enable_shared_from_this<TempSharedListener>& cref(*tempListener);
std::cout << "Capturing const ptr" << std::endl;
const boost::enable_shared_from_this<TempSharedListener>* cp(&cref);
std::cout << "Capturing non-const ptr" << std::endl;
boost::enable_shared_from_this<TempSharedListener>* p(const_cast<boost::enable_shared_from_this<TempSharedListener>*>(cp));
std::cout << "Capturing shared_from_this()" << std::endl;
boost::shared_ptr<TempSharedListener> sp(p->shared_from_this());
std::cout << "Capturing weak_ptr" << std::endl;
boost::weak_ptr<TempSharedListener> wp(weaken(sp));
std::cout << "Binding weak_ptr" << std::endl;
|*==========================================================================*/
connection = heaptest.listen(tempListener->getName(),
boost::bind(&TempSharedListener::call, *tempListener, _1));
heaptest.post(1);
check_listener("received", *tempListener, 1);
} // presumably this will make tempListener go away?
// verify that
ensure("TempSharedListener destroyed", ! live);
ensure("implicit disconnect", ! connection.connected());
// now just make sure we don't blow up trying to access a freed object!
heaptest.post(2);
#endif // 0
}
} // namespace tut

34
indra/test/lltestapp.h Normal file
View File

@ -0,0 +1,34 @@
/**
* @file lltestapp.h
* @author Nat Goodspeed
* @date 2019-10-21
* @brief LLApp subclass useful for testing.
*
* $LicenseInfo:firstyear=2019&license=viewerlgpl$
* Copyright (c) 2019, Linden Research, Inc.
* $/LicenseInfo$
*/
#if ! defined(LL_LLTESTAPP_H)
#define LL_LLTESTAPP_H
#include "llapp.h"
/**
* LLTestApp is a dummy LLApp that simply sets LLApp::isRunning() for anyone
* who cares.
*/
class LLTestApp: public LLApp
{
public:
LLTestApp()
{
setStatus(APP_STATUS_RUNNING);
}
bool init() { return true; }
bool cleanup() { return true; }
bool frame() { return true; }
};
#endif /* ! defined(LL_LLTESTAPP_H) */

View File

@ -625,6 +625,9 @@ int main(int argc, char **argv)
wait_at_exit = true;
break;
case 'd':
// this is what LLError::initForApplication() does internally
// when you pass log_to_stderr=true
LLError::logToStderr();
LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
break;
case 'x':

View File

@ -42,6 +42,7 @@
// other Linden headers
#include "llsd.h"
#include "../../../test/lltut.h"
#include "../../../test/lltestapp.h"
//#define DEBUG_ON
#include "../../../test/debug.h"
#include "llevents.h"
@ -201,6 +202,7 @@ namespace tut
pumps.clear();
}
LLEventPumps& pumps;
LLTestApp testApp;
};
typedef test_group<llviewerlogin_data> llviewerlogin_group;