master
Ansariel 2022-06-18 15:53:35 +02:00
commit 8ac67e5476
26 changed files with 732 additions and 231 deletions

View File

@ -29,6 +29,7 @@ include_directories( SYSTEM
# ${LLCOMMON_LIBRARIES})
set(llcommon_SOURCE_FILES
commoncontrol.cpp
indra_constants.cpp
llallocator.cpp
llallocator_heap_profile.cpp
@ -129,6 +130,7 @@ set(llcommon_HEADER_FILES
CMakeLists.txt
chrono.h
commoncontrol.h
ctype_workaround.h
fix_macros.h
indra_constants.h

View File

@ -0,0 +1,106 @@
/**
* @file commoncontrol.cpp
* @author Nat Goodspeed
* @date 2022-06-08
* @brief Implementation for commoncontrol.
*
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
* Copyright (c) 2022, Linden Research, Inc.
* $/LicenseInfo$
*/
// Precompiled header
#include "linden_common.h"
// associated header
#include "commoncontrol.h"
// STL headers
// std headers
// external library headers
// other Linden headers
#include "llevents.h"
#include "llsdutil.h"
LLSD LL::CommonControl::access(const LLSD& params)
{
// We can't actually introduce a link-time dependency on llxml, or on any
// global LLControlGroup (*koff* gSavedSettings *koff*) but we can issue a
// runtime query. If we're running as part of a viewer with
// LLViewerControlListener, we can use that to interact with any
// instantiated LLControGroup.
LLSD response;
{
LLEventStream reply("reply");
LLTempBoundListener connection = reply.listen("listener",
[&response] (const LLSD& event)
{
response = event;
return false;
});
LLSD rparams{ params };
rparams["reply"] = reply.getName();
LLEventPumps::instance().obtain("LLViewerControl").post(rparams);
}
// LLViewerControlListener responds immediately. If it's listening at all,
// it will already have set response.
if (! response.isDefined())
{
LLTHROW(NoListener("No LLViewerControl listener instantiated"));
}
LLSD error{ response["error"] };
if (error.isDefined())
{
LLTHROW(ParamError(error));
}
response.erase("error");
response.erase("reqid");
return response;
}
/// set control group.key to defined default value
LLSD LL::CommonControl::set_default(const std::string& group, const std::string& key)
{
return access(llsd::map("op", "set",
"group", group, "key", key))["value"];
}
/// set control group.key to specified value
LLSD LL::CommonControl::set(const std::string& group, const std::string& key, const LLSD& value)
{
return access(llsd::map("op", "set",
"group", group, "key", key, "value", value))["value"];
}
/// toggle boolean control group.key
LLSD LL::CommonControl::toggle(const std::string& group, const std::string& key)
{
return access(llsd::map("op", "toggle",
"group", group, "key", key))["value"];
}
/// get the definition for control group.key, (! isDefined()) if bad
/// ["name"], ["type"], ["value"], ["comment"]
LLSD LL::CommonControl::get_def(const std::string& group, const std::string& key)
{
return access(llsd::map("op", "get",
"group", group, "key", key));
}
/// get the value of control group.key
LLSD LL::CommonControl::get(const std::string& group, const std::string& key)
{
return access(llsd::map("op", "get",
"group", group, "key", key))["value"];
}
/// get defined groups
std::vector<std::string> LL::CommonControl::get_groups()
{
auto groups{ access(llsd::map("op", "groups"))["groups"] };
return { groups.beginArray(), groups.endArray() };
}
/// get definitions for all variables in group
LLSD LL::CommonControl::get_vars(const std::string& group)
{
return access(llsd::map("op", "vars", "group", group))["vars"];
}

View File

@ -0,0 +1,75 @@
/**
* @file commoncontrol.h
* @author Nat Goodspeed
* @date 2022-06-08
* @brief Access LLViewerControl LLEventAPI, if process has one.
*
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
* Copyright (c) 2022, Linden Research, Inc.
* $/LicenseInfo$
*/
#if ! defined(LL_COMMONCONTROL_H)
#define LL_COMMONCONTROL_H
#include <vector>
#include "llexception.h"
#include "llsd.h"
namespace LL
{
class CommonControl
{
public:
struct Error: public LLException
{
Error(const std::string& what): LLException(what) {}
};
/// Exception thrown if there's no LLViewerControl LLEventAPI
struct NoListener: public Error
{
NoListener(const std::string& what): Error(what) {}
};
struct ParamError: public Error
{
ParamError(const std::string& what): Error(what) {}
};
/// set control group.key to defined default value
static
LLSD set_default(const std::string& group, const std::string& key);
/// set control group.key to specified value
static
LLSD set(const std::string& group, const std::string& key, const LLSD& value);
/// toggle boolean control group.key
static
LLSD toggle(const std::string& group, const std::string& key);
/// get the definition for control group.key, (! isDefined()) if bad
/// ["name"], ["type"], ["value"], ["comment"]
static
LLSD get_def(const std::string& group, const std::string& key);
/// get the value of control group.key
static
LLSD get(const std::string& group, const std::string& key);
/// get defined groups
static
std::vector<std::string> get_groups();
/// get definitions for all variables in group
static
LLSD get_vars(const std::string& group);
private:
static
LLSD access(const LLSD& params);
};
} // namespace LL
#endif /* ! defined(LL_COMMONCONTROL_H) */

View File

@ -35,6 +35,7 @@
# include <sys/types.h>
# include <mach/task.h>
# include <mach/mach_init.h>
#include <mach/mach_host.h>
#elif LL_LINUX
# include <unistd.h>
#endif
@ -109,6 +110,50 @@ void LLMemory::updateMemoryInfo()
{
sAvailPhysicalMemInKB = U32Kilobytes(0);
}
#elif defined(LL_DARWIN)
task_vm_info info;
mach_msg_type_number_t infoCount = TASK_VM_INFO_COUNT;
// MACH_TASK_BASIC_INFO reports the same resident_size, but does not tell us the reusable bytes or phys_footprint.
if (task_info(mach_task_self(), TASK_VM_INFO, reinterpret_cast<task_info_t>(&info), &infoCount) == KERN_SUCCESS)
{
// Our Windows definition of PagefileUsage is documented by Microsoft as "the total amount of
// memory that the memory manager has committed for a running process", which is rss.
sAllocatedPageSizeInKB = U32Bytes(info.resident_size);
// Activity Monitor => Inspect Process => Real Memory Size appears to report resident_size
// Activity monitor => main window memory column appears to report phys_footprint, which spot checks as at least 30% less.
// I think that is because of compression, which isn't going to give us a consistent measurement. We want uncompressed totals.
//
// In between is resident_size - reusable. This is what Chrome source code uses, with source comments saying it is 'the "Real Memory" value
// reported for the app by the Memory Monitor in Instruments.' It is still about 8% bigger than phys_footprint.
//
// (On Windows, we use WorkingSetSize.)
sAllocatedMemInKB = U32Bytes(info.resident_size - info.reusable);
}
else
{
LL_WARNS() << "task_info failed" << LL_ENDL;
}
// Total installed and available physical memory are properties of the host, not just our process.
vm_statistics64_data_t vmstat;
mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
mach_port_t host = mach_host_self();
vm_size_t page_size;
host_page_size(host, &page_size);
kern_return_t result = host_statistics64(host, HOST_VM_INFO64, reinterpret_cast<host_info_t>(&vmstat), &count);
if (result == KERN_SUCCESS) {
// This is what Chrome reports as 'the "Physical Memory Free" value reported by the Memory Monitor in Instruments.'
// Note though that inactive pages are not included here and not yet free, but could become so under memory pressure.
sAvailPhysicalMemInKB = U32Bytes(vmstat.free_count * page_size);
sMaxPhysicalMemInKB = LLMemoryInfo::getHardwareMemSize();
}
else
{
LL_WARNS() << "task_info failed" << LL_ENDL;
}
#else
//not valid for other systems for now.
sAllocatedMemInKB = U64Bytes(LLMemory::getCurrentRSS());

View File

@ -695,20 +695,28 @@ static U32Kilobytes LLMemoryAdjustKBResult(U32Kilobytes inKB)
}
#endif
#if LL_DARWIN
// static
U32Kilobytes LLMemoryInfo::getHardwareMemSize()
{
// This might work on Linux as well. Someone check...
uint64_t phys = 0;
int mib[2] = { CTL_HW, HW_MEMSIZE };
size_t len = sizeof(phys);
sysctl(mib, 2, &phys, &len, NULL, 0);
return U64Bytes(phys);
}
#endif
U32Kilobytes LLMemoryInfo::getPhysicalMemoryKB() const
{
#if LL_WINDOWS
return LLMemoryAdjustKBResult(U32Kilobytes(mStatsMap["Total Physical KB"].asInteger()));
#elif LL_DARWIN
// This might work on Linux as well. Someone check...
uint64_t phys = 0;
int mib[2] = { CTL_HW, HW_MEMSIZE };
size_t len = sizeof(phys);
sysctl(mib, 2, &phys, &len, NULL, 0);
return U64Bytes(phys);
return getHardwareMemSize();
#elif LL_LINUX
U64 phys = 0;

View File

@ -113,7 +113,10 @@ public:
LLMemoryInfo(); ///< Default constructor
void stream(std::ostream& s) const; ///< output text info to s
U32Kilobytes getPhysicalMemoryKB() const;
U32Kilobytes getPhysicalMemoryKB() const;
#if LL_DARWIN
static U32Kilobytes getHardwareMemSize(); // Because some Mac linkers won't let us reference extern gSysMemory from a different lib.
#endif
//get the available memory infomation in KiloBytes.
static void getAvailableMemoryKB(U32Kilobytes& avail_physical_mem_kb, U32Kilobytes& avail_virtual_mem_kb);

View File

@ -17,14 +17,17 @@
// std headers
// external library headers
// other Linden headers
#include "commoncontrol.h"
#include "llerror.h"
#include "llevents.h"
#include "llsd.h"
#include "stringize.h"
LL::ThreadPool::ThreadPool(const std::string& name, size_t threads, size_t capacity):
super(name),
mQueue(name, capacity),
mName("ThreadPool:" + name),
mThreadCount(threads)
mThreadCount(getConfiguredWidth(name, threads))
{}
void LL::ThreadPool::start()
@ -86,3 +89,58 @@ void LL::ThreadPool::run()
{
mQueue.runUntilClose();
}
//static
size_t LL::ThreadPool::getConfiguredWidth(const std::string& name, size_t dft)
{
LLSD poolSizes;
try
{
poolSizes = LL::CommonControl::get("Global", "ThreadPoolSizes");
// "ThreadPoolSizes" is actually a map containing the sizes of
// interest -- or should be, if this process has an
// LLViewerControlListener instance and its settings include
// "ThreadPoolSizes". If we failed to retrieve it, perhaps we're in a
// program that doesn't define that, or perhaps there's no such
// setting, or perhaps we're asking too early, before the LLEventAPI
// itself has been instantiated. In any of those cases, it seems worth
// warning.
if (! poolSizes.isDefined())
{
// Note: we don't warn about absence of an override key for a
// particular ThreadPool name, that's fine. This warning is about
// complete absence of a ThreadPoolSizes setting, which we expect
// in a normal viewer session.
LL_WARNS("ThreadPool") << "No 'ThreadPoolSizes' setting for ThreadPool '"
<< name << "'" << LL_ENDL;
}
}
catch (const LL::CommonControl::Error& exc)
{
// We don't want ThreadPool to *require* LLViewerControlListener.
// Just log it and carry on.
LL_WARNS("ThreadPool") << "Can't check 'ThreadPoolSizes': " << exc.what() << LL_ENDL;
}
LL_DEBUGS("ThreadPool") << "ThreadPoolSizes = " << poolSizes << LL_ENDL;
// LLSD treats an undefined value as an empty map when asked to retrieve a
// key, so we don't need this to be conditional.
LLSD sizeSpec{ poolSizes[name] };
// We retrieve sizeSpec as LLSD, rather than immediately as LLSD::Integer,
// so we can distinguish the case when it's undefined.
return sizeSpec.isInteger() ? sizeSpec.asInteger() : dft;
}
//static
size_t LL::ThreadPool::getWidth(const std::string& name, size_t dft)
{
auto instance{ getInstance(name) };
if (instance)
{
return instance->getWidth();
}
else
{
return getConfiguredWidth(name, dft);
}
}

View File

@ -22,14 +22,25 @@
namespace LL
{
class ThreadPool
class ThreadPool: public LLInstanceTracker<ThreadPool, std::string>
{
private:
using super = LLInstanceTracker<ThreadPool, std::string>;
public:
/**
* Pass ThreadPool a string name. This can be used to look up the
* relevant WorkQueue.
*
* The number of threads you pass sets the compile-time default. But
* if the user has overridden the LLSD map in the "ThreadPoolSizes"
* setting with a key matching this ThreadPool name, that setting
* overrides this parameter.
*
* Pass an explicit capacity to limit the size of the queue.
* Constraining the queue can cause a submitter to block. Do not
* constrain any ThreadPool accepting work from the main thread.
*/
ThreadPool(const std::string& name, size_t threads=1, size_t capacity=1024);
ThreadPool(const std::string& name, size_t threads=1, size_t capacity=1024*1024);
virtual ~ThreadPool();
/**
@ -57,6 +68,25 @@ namespace LL
*/
virtual void run();
/**
* getConfiguredWidth() returns the setting, if any, for the specified
* ThreadPool name. Returns dft if the "ThreadPoolSizes" map does not
* contain the specified name.
*/
static
size_t getConfiguredWidth(const std::string& name, size_t dft=0);
/**
* This getWidth() returns the width of the instantiated ThreadPool
* with the specified name, if any. If no instance exists, returns its
* getConfiguredWidth() if any. If there's no instance and no relevant
* override, return dft. Presumably dft should match the threads
* parameter passed to the ThreadPool constructor call that will
* eventually instantiate the ThreadPool with that name.
*/
static
size_t getWidth(const std::string& name, size_t dft);
private:
void run(const std::string& name);

View File

@ -28,44 +28,88 @@
#include "llimageworker.h"
#include "llimagedxt.h"
#include "threadpool.h"
/*--------------------------------------------------------------------------*/
class ImageRequest
{
public:
ImageRequest(const LLPointer<LLImageFormatted>& image,
S32 discard, BOOL needs_aux,
const LLPointer<LLImageDecodeThread::Responder>& responder);
virtual ~ImageRequest();
/*virtual*/ bool processRequest();
/*virtual*/ void finishRequest(bool completed);
private:
// LLPointers stored in ImageRequest MUST be LLPointer instances rather
// than references: we need to increment the refcount when storing these.
// input
LLPointer<LLImageFormatted> mFormattedImage;
S32 mDiscardLevel;
BOOL mNeedsAux;
// output
LLPointer<LLImageRaw> mDecodedImageRaw;
LLPointer<LLImageRaw> mDecodedImageAux;
BOOL mDecodedRaw;
BOOL mDecodedAux;
LLPointer<LLImageDecodeThread::Responder> mResponder;
};
//----------------------------------------------------------------------------
// MAIN THREAD
LLImageDecodeThread::LLImageDecodeThread(bool threaded)
: LLQueuedThread("imagedecode", threaded)
LLImageDecodeThread::LLImageDecodeThread(bool /*threaded*/)
{
mCreationMutex = new LLMutex();
mThreadPool.reset(new LL::ThreadPool("ImageDecode", 8));
mThreadPool->start();
}
//virtual
LLImageDecodeThread::~LLImageDecodeThread()
{
delete mCreationMutex ;
}
{}
// MAIN THREAD
// virtual
S32 LLImageDecodeThread::update(F32 max_time_ms)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
S32 res = LLQueuedThread::update(max_time_ms);
return res;
return getPending();
}
LLImageDecodeThread::handle_t LLImageDecodeThread::decodeImage(LLImageFormatted* image,
S32 discard, BOOL needs_aux, Responder* responder)
S32 LLImageDecodeThread::getPending()
{
return mThreadPool->getQueue().size();
}
LLImageDecodeThread::handle_t LLImageDecodeThread::decodeImage(
const LLPointer<LLImageFormatted>& image,
S32 discard,
BOOL needs_aux,
const LLPointer<LLImageDecodeThread::Responder>& responder)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
handle_t handle = generateHandle();
ImageRequest* req = new ImageRequest(handle, image,
discard, needs_aux,
responder);
// Instantiate the ImageRequest right in the lambda, why not?
mThreadPool->getQueue().post(
[req = ImageRequest(image, discard, needs_aux, responder)]
() mutable
{
auto done = req.processRequest();
req.finishRequest(done);
});
addRequest(req);
// It's important to our consumer (LLTextureFetchWorker) that we return a
// nonzero handle. It is NOT important that the nonzero handle be unique:
// nothing is ever done with it except to compare it to zero, or zero it.
return 17;
}
return handle;
void LLImageDecodeThread::shutdown()
{
mThreadPool->close();
}
LLImageDecodeThread::Responder::~Responder()
@ -74,11 +118,10 @@ LLImageDecodeThread::Responder::~Responder()
//----------------------------------------------------------------------------
LLImageDecodeThread::ImageRequest::ImageRequest(handle_t handle, LLImageFormatted* image,
S32 discard, BOOL needs_aux,
LLImageDecodeThread::Responder* responder)
: LLQueuedThread::QueuedRequest(handle, FLAG_AUTO_COMPLETE),
mFormattedImage(image),
ImageRequest::ImageRequest(const LLPointer<LLImageFormatted>& image,
S32 discard, BOOL needs_aux,
const LLPointer<LLImageDecodeThread::Responder>& responder)
: mFormattedImage(image),
mDiscardLevel(discard),
mNeedsAux(needs_aux),
mDecodedRaw(FALSE),
@ -87,7 +130,7 @@ LLImageDecodeThread::ImageRequest::ImageRequest(handle_t handle, LLImageFormatte
{
}
LLImageDecodeThread::ImageRequest::~ImageRequest()
ImageRequest::~ImageRequest()
{
mDecodedImageRaw = NULL;
mDecodedImageAux = NULL;
@ -98,7 +141,7 @@ LLImageDecodeThread::ImageRequest::~ImageRequest()
// Returns true when done, whether or not decode was successful.
bool LLImageDecodeThread::ImageRequest::processRequest()
bool ImageRequest::processRequest()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
const F32 decode_time_slice = 0.f; //disable time slicing
@ -127,9 +170,9 @@ bool LLImageDecodeThread::ImageRequest::processRequest()
}
// <FS:ND> Probably out of memory crash
// done = mFormattedImage->decode(mDecodedImageRaw, decode_time_slice); // 1ms
// done = mFormattedImage->decode(mDecodedImageRaw, decode_time_slice);
if( mDecodedImageRaw->getData() )
done = mFormattedImage->decode(mDecodedImageRaw, decode_time_slice); // 1ms
done = mFormattedImage->decode(mDecodedImageRaw, decode_time_slice);
else
{
LL_WARNS() << "No memory for LLImageRaw of size " << (U32)mFormattedImage->getWidth() << "x" << (U32)mFormattedImage->getHeight() << "x"
@ -150,14 +193,14 @@ bool LLImageDecodeThread::ImageRequest::processRequest()
mFormattedImage->getHeight(),
1);
}
done = mFormattedImage->decodeChannels(mDecodedImageAux, decode_time_slice, 4, 4); // 1ms
done = mFormattedImage->decodeChannels(mDecodedImageAux, decode_time_slice, 4, 4);
mDecodedAux = done && mDecodedImageAux->getData();
}
return done;
}
void LLImageDecodeThread::ImageRequest::finishRequest(bool completed)
void ImageRequest::finishRequest(bool completed)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
if (mResponder.notNull())
@ -167,10 +210,3 @@ void LLImageDecodeThread::ImageRequest::finishRequest(bool completed)
}
// Will automatically be deleted
}
// Used by unit test only
// Checks that a responder exists for this instance so that something can happen when completion is reached
bool LLImageDecodeThread::ImageRequest::tut_isOK()
{
return mResponder.notNull();
}

View File

@ -29,9 +29,13 @@
#include "llimage.h"
#include "llpointer.h"
#include "llworkerthread.h"
class LLImageDecodeThread : public LLQueuedThread
namespace LL
{
class ThreadPool;
} // namespace LL
class LLImageDecodeThread
{
public:
class Responder : public LLThreadSafeRefCount
@ -42,57 +46,24 @@ public:
virtual void completed(bool success, LLImageRaw* raw, LLImageRaw* aux) = 0;
};
class ImageRequest : public LLQueuedThread::QueuedRequest
{
protected:
virtual ~ImageRequest(); // use deleteRequest()
public:
ImageRequest(handle_t handle, LLImageFormatted* image,
S32 discard, BOOL needs_aux,
LLImageDecodeThread::Responder* responder);
/*virtual*/ bool processRequest();
/*virtual*/ void finishRequest(bool completed);
// Used by unit tests to check the consitency of the request instance
bool tut_isOK();
private:
// input
LLPointer<LLImageFormatted> mFormattedImage;
S32 mDiscardLevel;
BOOL mNeedsAux;
// output
LLPointer<LLImageRaw> mDecodedImageRaw;
LLPointer<LLImageRaw> mDecodedImageAux;
BOOL mDecodedRaw;
BOOL mDecodedAux;
LLPointer<LLImageDecodeThread::Responder> mResponder;
};
public:
LLImageDecodeThread(bool threaded = true);
virtual ~LLImageDecodeThread();
handle_t decodeImage(LLImageFormatted* image,
// meant to resemble LLQueuedThread::handle_t
typedef U32 handle_t;
handle_t decodeImage(const LLPointer<LLImageFormatted>& image,
S32 discard, BOOL needs_aux,
Responder* responder);
const LLPointer<Responder>& responder);
S32 getPending();
S32 update(F32 max_time_ms);
void shutdown();
private:
struct creation_info
{
handle_t handle;
LLPointer<LLImageFormatted> image;
S32 discard;
BOOL needs_aux;
LLPointer<Responder> responder;
creation_info(handle_t h, LLImageFormatted* i, U32 p, S32 d, BOOL aux, Responder* r)
: handle(h), image(i), discard(d), needs_aux(aux), responder(r)
{}
};
LLMutex* mCreationMutex;
// As of SL-17483, LLImageDecodeThread is no longer itself an
// LLQueuedThread - instead this is the API by which we submit work to the
// "ImageDecode" ThreadPool.
std::unique_ptr<LL::ThreadPool> mThreadPool;
};
#endif

View File

@ -125,42 +125,11 @@ namespace tut
}
};
// Test wrapper declaration : image worker
// Note: this class is not meant to be instantiated outside an LLImageDecodeThread instance
// but it's not a bad idea to get its public API a good shake as part of a thorough unit test set.
// Some gotcha with the destructor though (see below).
struct imagerequest_test
{
// Instance to be tested
LLImageDecodeThread::ImageRequest* mRequest;
bool done;
// Constructor and destructor of the test wrapper
imagerequest_test()
{
done = false;
mRequest = new LLImageDecodeThread::ImageRequest(0, 0,
0, FALSE,
new responder_test(&done));
}
~imagerequest_test()
{
// We should delete the object *but*, because its destructor is protected, that cannot be
// done from outside an LLImageDecodeThread instance... So we leak memory here... It's fine...
//delete mRequest;
}
};
// Tut templating thingamagic: test group, object and test instance
typedef test_group<imagedecodethread_test> imagedecodethread_t;
typedef imagedecodethread_t::object imagedecodethread_object_t;
tut::imagedecodethread_t tut_imagedecodethread("LLImageDecodeThread");
typedef test_group<imagerequest_test> imagerequest_t;
typedef imagerequest_t::object imagerequest_object_t;
tut::imagerequest_t tut_imagerequest("LLImageRequest");
// ---------------------------------------------------------------------------------------
// Test functions
// Notes:
@ -172,21 +141,6 @@ namespace tut
// ---------------------------------------------------------------------------------------
// Test the LLImageDecodeThread interface
// ---------------------------------------------------------------------------------------
//
// Note on Unit Testing Queued Thread Classes
//
// Since methods on such a class are called on a separate loop and that we can't insert tut
// ensure() calls in there, we exercise the class with 2 sets of tests:
// - 1: Test as a single threaded instance: We declare the class but ask for no thread
// to be spawned (easy with LLThreads since there's a boolean argument on the constructor
// just for that). We can then unit test each public method like we do on a normal class.
// - 2: Test as a threaded instance: We let the thread launch and check that its external
// behavior is as expected (i.e. it runs, can accept a work order and processes
// it). Typically though there's no guarantee that this exercises all the methods of the
// class which is why we also need the previous "non threaded" set of unit tests for
// complete coverage.
//
// ---------------------------------------------------------------------------------------
template<> template<>
void imagedecodethread_object_t::test<1>()
@ -211,24 +165,4 @@ namespace tut
// Verifies that the responder has now been called
ensure("LLImageDecodeThread: threaded work unit not processed", done == true);
}
// ---------------------------------------------------------------------------------------
// Test the LLImageDecodeThread::ImageRequest interface
// ---------------------------------------------------------------------------------------
template<> template<>
void imagerequest_object_t::test<1>()
{
// Test that we start with a correct request at creation
ensure("LLImageDecodeThread::ImageRequest::ImageRequest() constructor test failed", mRequest->tut_isOK());
bool res = mRequest->processRequest();
// Verifies that we processed the request successfully
ensure("LLImageDecodeThread::ImageRequest::processRequest() processing request test failed", res == true);
// Check that we can call the finishing call safely
try {
mRequest->finishRequest(false);
} catch (...) {
fail("LLImageDecodeThread::ImageRequest::finishRequest() test failed");
}
}
}

View File

@ -2447,10 +2447,8 @@ void LLImageGL::checkActiveThread()
*/
LLImageGLThread::LLImageGLThread(LLWindow* window)
// We want exactly one thread, but a very large capacity: we never want
// anyone, especially inner-loop render code, to have to block on post()
// because we're full.
: ThreadPool("LLImageGL", 1, 1024*1024)
// We want exactly one thread.
: ThreadPool("LLImageGL", 1)
, mWindow(window)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;

View File

@ -141,7 +141,12 @@ attributedStringInfo getSegments(NSAttributedString *str)
CGLError the_err = CGLQueryRendererInfo (CGDisplayIDToOpenGLDisplayMask(kCGDirectMainDisplay), &info, &num_renderers);
if(0 == the_err)
{
CGLDescribeRenderer (info, 0, kCGLRPTextureMemoryMegabytes, &vram_megabytes);
// The name, uses, and other platform definitions of gGLManager.mVRAM suggest that this is supposed to be total vram in MB,
// rather than, say, just the texture memory. The two exceptions are:
// 1. LLAppViewer::getViewerInfo() puts the value in a field labeled "TEXTURE_MEMORY"
// 2. For years, this present function used kCGLRPTextureMemoryMegabytes
// Now we use kCGLRPVideoMemoryMegabytes to bring it in line with everything else (except thatone label).
CGLDescribeRenderer (info, 0, kCGLRPVideoMemoryMegabytes, &vram_megabytes);
CGLDestroyRendererInfo (info);
}
else

View File

@ -1232,6 +1232,16 @@ F32 LLWindowMacOSX::getPixelAspectRatio()
return 1.f;
}
U32 LLWindowMacOSX::getAvailableVRAMMegabytes() {
// MTL (and MoltenVK) has some additional gpu data, such as recommendedMaxWorkingSetSize and currentAllocatedSize.
// But these are not available for OpenGL and/or our current mimimum OS version.
// So we will estimate.
static const U32 mb = 1024*1024;
// We're asked for total available gpu memory, but we only have allocation info on texture usage. So estimate by doubling that.
static const U32 total_factor = 2; // estimated total/textures
return gGLManager.mVRAM - (LLImageGL::getTextureBytesAllocated() * total_factor/mb);
}
//static SInt32 oldWindowLevel;
// MBW -- XXX -- There's got to be a better way than this. Find it, please...

View File

@ -101,8 +101,7 @@ public:
void setNativeAspectRatio(F32 ratio) override { mOverrideAspectRatio = ratio; }
// query VRAM usage
// FIXME FIXME
virtual U32 getAvailableVRAMMegabytes() override { return 0; }
/*virtual*/ U32 getAvailableVRAMMegabytes() override;
void beforeDialog() override;
void afterDialog() override;

View File

@ -65,7 +65,6 @@
#include <d3d9.h>
#include <dxgi1_4.h>
// Require DirectInput version 8
#define DIRECTINPUT_VERSION 0x0800
@ -4763,23 +4762,34 @@ void LLWindowWin32::LLWindowWin32Thread::updateVRAMUsage()
mDXGIAdapter->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP_LOCAL, &info);
// try to use no more than the available reserve minus 10%
U32 target = info.AvailableForReservation / 1024 / 1024;
target -= target / 10;
U32 target = info.Budget / 1024 / 1024;
// EXPERIMENTAL
// Trying to zero in on a good target usage, code here should be tuned against observed behavior
// of various hardware.
if (target > 4096) // if 4GB are installed, try to leave 2GB free
{
target -= 2048;
}
else // if less than 4GB are installed, try not to use more than half of it
{
target /= 2;
}
U32 used_vram = info.CurrentUsage / 1024 / 1024;
mAvailableVRAM = used_vram < target ? target - used_vram : 0;
/*LL_INFOS() << "\nLocal\nAFR: " << info.AvailableForReservation / 1024 / 1024
LL_INFOS("Window") << "\nLocal\nAFR: " << info.AvailableForReservation / 1024 / 1024
<< "\nBudget: " << info.Budget / 1024 / 1024
<< "\nCR: " << info.CurrentReservation / 1024 / 1024
<< "\nCU: " << info.CurrentUsage / 1024 / 1024 << LL_ENDL;
mDXGIAdapter->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL, &info);
LL_INFOS() << "\nNon-Local\nAFR: " << info.AvailableForReservation / 1024 / 1024
LL_INFOS("Window") << "\nNon-Local\nAFR: " << info.AvailableForReservation / 1024 / 1024
<< "\nBudget: " << info.Budget / 1024 / 1024
<< "\nCR: " << info.CurrentReservation / 1024 / 1024
<< "\nCU: " << info.CurrentUsage / 1024 / 1024 << LL_ENDL;*/
<< "\nCU: " << info.CurrentUsage / 1024 / 1024 << LL_ENDL;
}
else if (mD3DDevice != NULL)
{ // fallback to D3D9

View File

@ -3035,6 +3035,19 @@ if (LL_TESTS)
# "${test_libs}"
# )
set(llviewercontrollistener_test_sources
llviewercontrollistener.cpp
../llxml/llcontrol.cpp
../llxml/llxmltree.cpp
../llxml/llxmlparser.cpp
../llcommon/commoncontrol.cpp
)
LL_ADD_INTEGRATION_TEST(llviewercontrollistener
"${llviewercontrollistener_test_sources}"
"${test_libs}"
)
LL_ADD_INTEGRATION_TEST(llviewernetwork
llviewernetwork.cpp
"${test_libs}"

View File

@ -15883,7 +15883,9 @@ Change of this parameter will affect the layout of buttons in notification toast
<key>Value</key>
<map>
<key>General</key>
<integer>4</integer>
<integer>1</integer>
<key>ImageDecode</key>
<integer>9</integer>
</map>
</map>
<key>ThrottleBandwidthKBPS</key>

View File

@ -1821,7 +1821,6 @@ bool LLAppViewer::doFrame()
{
S32 non_interactive_ms_sleep_time = 100;
LLAppViewer::getTextureCache()->pause();
LLAppViewer::getImageDecodeThread()->pause();
ms_sleep(non_interactive_ms_sleep_time);
}
@ -1842,7 +1841,6 @@ bool LLAppViewer::doFrame()
ms_sleep(milliseconds_to_sleep);
// also pause worker threads during this wait period
LLAppViewer::getTextureCache()->pause();
LLAppViewer::getImageDecodeThread()->pause();
}
}
@ -1896,7 +1894,6 @@ bool LLAppViewer::doFrame()
{
LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df getTextureCache" )
LLAppViewer::getTextureCache()->pause();
LLAppViewer::getImageDecodeThread()->pause();
LLAppViewer::getTextureFetch()->pause();
}
if(!total_io_pending) //pause file threads if nothing to process.
@ -2202,8 +2199,6 @@ bool LLAppViewer::cleanup()
LL_INFOS() << "Cache files removed" << LL_ENDL;
// Wait for any pending LFS IO
flushLFSIO();
LL_INFOS() << "Shutting down Views" << LL_ENDL;
// Destroy the UI
@ -2441,13 +2436,14 @@ bool LLAppViewer::cleanup()
sTextureCache->shutdown();
sImageDecodeThread->shutdown();
sPurgeDiskCacheThread->shutdown();
if (mGeneralThreadPool)
{
mGeneralThreadPool->close();
}
if (mGeneralThreadPool)
{
mGeneralThreadPool->close();
}
sTextureFetch->shutDownTextureCacheThread() ;
sTextureFetch->shutDownImageDecodeThread() ;
LLLFSThread::sLocal->shutdown();
LL_INFOS() << "Shutting down message system" << LL_ENDL;
end_messaging_system();
@ -2564,14 +2560,7 @@ void LLAppViewer::initGeneralThread()
return;
}
LLSD poolSizes{ gSavedSettings.getLLSD("ThreadPoolSizes") };
LLSD sizeSpec{ poolSizes["General"] };
LLSD::Integer poolSize{ sizeSpec.isInteger() ? sizeSpec.asInteger() : 3 };
LL_DEBUGS("ThreadPool") << "Instantiating General pool with "
<< poolSize << " threads" << LL_ENDL;
// We don't want anyone, especially the main thread, to have to block
// due to this ThreadPool being full.
mGeneralThreadPool = new LL::ThreadPool("General", poolSize, 1024 * 1024);
mGeneralThreadPool = new LL::ThreadPool("General", 3);
mGeneralThreadPool->start();
}

View File

@ -1073,7 +1073,7 @@ void LLTextureFetchWorker::setDesiredDiscard(S32 discard, S32 size)
// Locks: Mw
void LLTextureFetchWorker::setImagePriority(F32 priority)
{
mImagePriority = priority; //should map to max virtual size
mImagePriority = priority; //should map to max virtual size, abort if zero
}
// Locks: Mw
@ -1369,7 +1369,7 @@ bool LLTextureFetchWorker::doWork(S32 param)
{
if (mFTType != FTT_DEFAULT)
{
LL_WARNS(LOG_TXT) << "trying to seek a non-default texture on the sim. Bad!" << LL_ENDL;
LL_WARNS(LOG_TXT) << "Trying to fetch a texture of non-default type by UUID. This probably won't work!" << LL_ENDL;
}
setUrl(http_url + "/?texture_id=" + mID.asString().c_str());
LL_DEBUGS(LOG_TXT) << "Texture URL: " << mUrl << LL_ENDL;
@ -1418,7 +1418,7 @@ bool LLTextureFetchWorker::doWork(S32 param)
else
{
// Shouldn't need to do anything here
llassert(mFetcher->mNetworkQueue.find(mID) != mFetcher->mNetworkQueue.end());
//llassert(mFetcher->mNetworkQueue.find(mID) != mFetcher->mNetworkQueue.end());
return false;
}
}
@ -1692,7 +1692,7 @@ bool LLTextureFetchWorker::doWork(S32 param)
<< LL_ENDL;
}
if (mFTType != FTT_SERVER_BAKE)
if (mFTType != FTT_SERVER_BAKE && mFTType != FTT_MAP_TILE)
{
mUrl.clear();
}
@ -2134,10 +2134,10 @@ void LLTextureFetchWorker::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRe
// Threads: Tmain
void LLTextureFetchWorker::endWork(S32 param, bool aborted)
{
LL_PROFILE_ZONE_SCOPED;
LL_PROFILE_ZONE_SCOPED;
if (mDecodeHandle != 0)
{
mFetcher->mImageDecodeThread->abortRequest(mDecodeHandle, false);
// LL::ThreadPool has no operation to cancel a particular work item
mDecodeHandle = 0;
}
mFormattedImage = NULL;
@ -2716,6 +2716,11 @@ bool LLTextureFetch::createRequest(FTType f_type, const std::string& url, const
return false; // need to wait for previous aborted request to complete
}
worker->lockWorkMutex(); // +Mw
if (worker->mState == LLTextureFetchWorker::DONE && worker->mDesiredSize == llmax(desired_size, TEXTURE_CACHE_ENTRY_SIZE) && worker->mDesiredDiscard == desired_discard) {
worker->unlockWorkMutex(); // -Mw
return false; // similar request has failed or is in a transitional state
}
worker->mActiveCount++;
worker->mNeedsAux = needs_aux;
worker->setImagePriority(priority);
@ -3032,16 +3037,18 @@ bool LLTextureFetch::getRequestFinished(const LLUUID& id, S32& discard_level,
bool LLTextureFetch::updateRequestPriority(const LLUUID& id, F32 priority)
{
LL_PROFILE_ZONE_SCOPED;
bool res = false;
LLTextureFetchWorker* worker = getWorker(id);
if (worker)
{
worker->lockWorkMutex(); // +Mw
worker->setImagePriority(priority);
worker->unlockWorkMutex(); // -Mw
res = true;
}
return res;
mRequestQueue.tryPost([=]()
{
LLTextureFetchWorker* worker = getWorker(id);
if (worker)
{
worker->lockWorkMutex(); // +Mw
worker->setImagePriority(priority);
worker->unlockWorkMutex(); // -Mw
}
});
return true;
}
// Replicates and expands upon the base class's
@ -3190,7 +3197,7 @@ void LLTextureFetch::shutDownImageDecodeThread()
{
if(mImageDecodeThread)
{
llassert_always(mImageDecodeThread->isQuitting() || mImageDecodeThread->isStopped()) ;
delete mImageDecodeThread;
mImageDecodeThread = NULL ;
}
}

View File

@ -553,7 +553,7 @@ void LLViewerTexture::getGPUMemoryForTextures(S32Megabytes &gpu, S32Megabytes &p
{
gpu_res = (S32Megabytes)gViewerWindow->getWindow()->getAvailableVRAMMegabytes();
//check main memory, only works for windows.
//check main memory, only works for windows and macos.
LLMemory::updateMemoryInfo();
physical_res = LLMemory::getAvailableMemKB();
@ -685,6 +685,11 @@ void LLViewerTexture::cleanup()
{
notifyAboutMissingAsset();
if (LLAppViewer::getTextureFetch())
{
LLAppViewer::getTextureFetch()->updateRequestPriority(mID, 0.f);
}
mFaceList[LLRender::DIFFUSE_MAP].clear();
mFaceList[LLRender::NORMAL_MAP].clear();
mFaceList[LLRender::SPECULAR_MAP].clear();
@ -830,14 +835,8 @@ void LLViewerTexture::addTextureStats(F32 virtual_size, BOOL needs_gltexture) co
}
virtual_size *= sTexelPixelRatio;
/*if (!mMaxVirtualSizeResetCounter)
{
//flag to reset the values because the old values are used.
resetMaxVirtualSizeResetCounter();
mMaxVirtualSize = virtual_size;
mNeedsGLTexture = needs_gltexture;
}
else*/ if (virtual_size > mMaxVirtualSize)
if (virtual_size > mMaxVirtualSize)
{
mMaxVirtualSize = virtual_size;
}
@ -1881,6 +1880,12 @@ void LLViewerFetchedTexture::updateVirtualSize()
return;
}
if (sDesiredDiscardBias > 0.f)
{
// running out of video memory, don't hold onto high res textures in the background
mMaxVirtualSize = 0.f;
}
for (U32 ch = 0; ch < LLRender::NUM_TEXTURE_CHANNELS; ++ch)
{
llassert(mNumFaces[ch] <= mFaceList[ch].size());
@ -1969,6 +1974,16 @@ bool LLViewerFetchedTexture::isActiveFetching()
return mFetchState > 7 && mFetchState < 10 && monitor_enabled; //in state of WAIT_HTTP_REQ or DECODE_IMAGE.
}
void LLViewerFetchedTexture::setBoostLevel(S32 level)
{
LLViewerTexture::setBoostLevel(level);
if (level >= LLViewerTexture::BOOST_HIGH)
{
mDesiredDiscardLevel = 0;
}
}
bool LLViewerFetchedTexture::updateFetch()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
@ -2016,6 +2031,11 @@ bool LLViewerFetchedTexture::updateFetch()
LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - in fast cache");
return false;
}
if (mGLTexturep.isNull())
{ // fix for crash inside getCurrentDiscardLevelForFetching (shouldn't happen but appears to be happening)
llassert(false);
return false;
}
S32 current_discard = getCurrentDiscardLevelForFetching();
S32 desired_discard = getDesiredDiscardLevel();
@ -2284,8 +2304,11 @@ bool LLViewerFetchedTexture::updateFetch()
mFetchPriority, mFetchDeltaTime, mRequestDeltaTime, mCanUseHTTP);
}
// if createRequest() failed, we're finishing up a request for this UUID,
// wait for it to complete
// If createRequest() failed, that means one of two things:
// 1. We're finishing up a request for this UUID, so we
// should wait for it to complete
// 2. We've failed a request for this UUID, so there is
// no need to create another request
}
else if (mHasFetcher && !mIsFetching)
{

View File

@ -140,7 +140,7 @@ public:
/*virtual*/ bool isActiveFetching();
/*virtual*/ const LLUUID& getID() const { return mID; }
void setBoostLevel(S32 level);
virtual void setBoostLevel(S32 level);
S32 getBoostLevel() { return mBoostLevel; }
void setTextureListType(S32 tex_type) { mTextureListType = tex_type; }
S32 getTextureListType() { return mTextureListType; }
@ -334,10 +334,10 @@ public:
};
public:
/*virtual*/ S8 getType() const ;
/*virtual*/ S8 getType() const override;
FTType getFTType() const;
/*virtual*/ void forceImmediateUpdate() ;
/*virtual*/ void dump() ;
/*virtual*/ void forceImmediateUpdate() override;
/*virtual*/ void dump() override;
// Set callbacks to get called when the image gets updated with higher
// resolution versions.
@ -375,6 +375,7 @@ public:
S32 getDesiredDiscardLevel() { return mDesiredDiscardLevel; }
void setMinDiscardLevel(S32 discard) { mMinDesiredDiscardLevel = llmin(mMinDesiredDiscardLevel,(S8)discard); }
void setBoostLevel(S32 level) override;
bool updateFetch();
bool setDebugFetching(S32 debug_level);
bool isInDebug() const { return mInDebug; }
@ -387,14 +388,14 @@ public:
// Override the computation of discard levels if we know the exact output
// size of the image. Used for UI textures to not decode, even if we have
// more data.
/*virtual*/ void setKnownDrawSize(S32 width, S32 height);
/*virtual*/ void setKnownDrawSize(S32 width, S32 height) override;
// Set the debug text of all Viewer Objects associated with this texture
// to the specified text
void setDebugText(const std::string& text);
void setIsMissingAsset(BOOL is_missing = true);
/*virtual*/ BOOL isMissingAsset() const { return mIsMissingAsset; }
/*virtual*/ BOOL isMissingAsset() const override { return mIsMissingAsset; }
// returns dimensions of original image for local files (before power of two scaling)
// and returns 0 for all asset system images
@ -437,7 +438,7 @@ public:
BOOL isRawImageValid()const { return mIsRawImageValid ; }
void forceToSaveRawImage(S32 desired_discard = 0, F32 kept_time = 0.f) ;
void forceToRefetchTexture(S32 desired_discard = 0, F32 kept_time = 60.f);
/*virtual*/ void setCachedRawImage(S32 discard_level, LLImageRaw* imageraw) ;
/*virtual*/ void setCachedRawImage(S32 discard_level, LLImageRaw* imageraw) override;
void destroySavedRawImage() ;
LLImageRaw* getSavedRawImage() ;
BOOL hasSavedRawImage() const ;
@ -453,14 +454,14 @@ public:
void setInFastCacheList(bool in_list) { mInFastCacheList = in_list; }
bool isInFastCacheList() { return mInFastCacheList; }
/*virtual*/bool isActiveFetching(); //is actively in fetching by the fetching pipeline.
/*virtual*/bool isActiveFetching() override; //is actively in fetching by the fetching pipeline.
// <FS:Techwolf Lupindo> texture comment decoder
std::map<std::string,std::string> mComment;
// </FS:Techwolf Lupindo>
protected:
/*virtual*/ void switchToCachedImage();
/*virtual*/ void switchToCachedImage() override;
S32 getCurrentDiscardLevelForFetching() ;
private:

View File

@ -1037,7 +1037,7 @@ F32 LLViewerTextureList::updateImagesFetchTextures(F32 max_time)
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
LLTimer image_op_timer;
typedef std::vector<LLViewerFetchedTexture*> entries_list_t;
typedef std::vector<LLPointer<LLViewerFetchedTexture> > entries_list_t;
entries_list_t entries;
// update N textures at beginning of mImageList
@ -1069,10 +1069,13 @@ F32 LLViewerTextureList::updateImagesFetchTextures(F32 max_time)
}
}
for (auto* imagep : entries)
for (auto& imagep : entries)
{
updateImageDecodePriority(imagep);
imagep->updateFetch();
if (imagep->getNumRefs() > 1) // make sure this image hasn't been deleted before attempting to update (may happen as a side effect of some other image updating)
{
updateImageDecodePriority(imagep);
imagep->updateFetch();
}
}
if (entries.size() > 0)

View File

@ -6716,7 +6716,6 @@ void LLViewerWindow::stopGL(BOOL save_state)
// Pause texture decode threads (will get unpaused during main loop)
LLAppViewer::getTextureCache()->pause();
LLAppViewer::getImageDecodeThread()->pause();
LLAppViewer::getTextureFetch()->pause();
gSky.destroyGL();

View File

@ -0,0 +1,174 @@
/**
* @file llviewercontrollistener_test.cpp
* @author Nat Goodspeed
* @date 2022-06-09
* @brief Test for llviewercontrollistener.
*
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
* Copyright (c) 2022, Linden Research, Inc.
* $/LicenseInfo$
*/
// Precompiled header
#include "llviewerprecompiledheaders.h"
// associated header
#include "llviewercontrollistener.h"
// STL headers
// std headers
// external library headers
// other Linden headers
#include "../test/lltut.h"
#include "../test/catch_and_store_what_in.h" // catch_what()
#include "commoncontrol.h"
#include "llcontrol.h" // LLControlGroup
#include "llviewercontrollistener.h"
/*****************************************************************************
* TUT
*****************************************************************************/
namespace tut
{
void ensure_contains(const std::string& msg, const std::string& substr)
{
ensure_contains("Exception does not contain " + substr, msg, substr);
}
struct llviewercontrollistener_data
{
LLControlGroup Global{"FakeGlobal"};
llviewercontrollistener_data()
{
Global.declareString("strvar", "woof", "string variable");
// together we will stroll the boolvar, ma cherie
Global.declareBOOL("boolvar", TRUE, "bool variable");
}
};
typedef test_group<llviewercontrollistener_data> llviewercontrollistener_group;
typedef llviewercontrollistener_group::object object;
llviewercontrollistener_group llviewercontrollistenergrp("llviewercontrollistener");
template<> template<>
void object::test<1>()
{
set_test_name("CommonControl no listener");
// Not implemented: the linker drags in LLViewerControlListener when
// we bring in LLViewerControl.
}
template<> template<>
void object::test<2>()
{
set_test_name("CommonControl bad group");
std::string threw{ catch_what<LL::CommonControl::ParamError>(
[](){ LL::CommonControl::get("Nonexistent", "Variable"); }) };
ensure_contains(threw, "group");
ensure_contains(threw, "Nonexistent");
}
template<> template<>
void object::test<3>()
{
set_test_name("CommonControl bad variable");
std::string threw{ catch_what<LL::CommonControl::ParamError>(
[](){ LL::CommonControl::get("FakeGlobal", "Nonexistent"); }) };
ensure_contains(threw, "key");
ensure_contains(threw, "Nonexistent");
}
template<> template<>
void object::test<4>()
{
set_test_name("CommonControl toggle string");
std::string threw{ catch_what<LL::CommonControl::ParamError>(
[](){ LL::CommonControl::toggle("FakeGlobal", "strvar"); }) };
ensure_contains(threw, "non-boolean");
ensure_contains(threw, "strvar");
}
template<> template<>
void object::test<5>()
{
set_test_name("CommonControl list bad group");
std::string threw{ catch_what<LL::CommonControl::ParamError>(
[](){ LL::CommonControl::get_vars("Nonexistent"); }) };
ensure_contains(threw, "group");
ensure_contains(threw, "Nonexistent");
}
template<> template<>
void object::test<6>()
{
set_test_name("CommonControl get");
auto strvar{ LL::CommonControl::get("FakeGlobal", "strvar") };
ensure_equals(strvar, "woof");
auto boolvar{ LL::CommonControl::get("FakeGlobal", "boolvar") };
ensure(boolvar);
}
template<> template<>
void object::test<7>()
{
set_test_name("CommonControl set, set_default, toggle");
std::string newstr{ LL::CommonControl::set("FakeGlobal", "strvar", "mouse").asString() };
ensure_equals(newstr, "mouse");
ensure_equals(LL::CommonControl::get("FakeGlobal", "strvar").asString(), "mouse");
ensure_equals(LL::CommonControl::set_default("FakeGlobal", "strvar").asString(), "woof");
bool newbool{ LL::CommonControl::set("FakeGlobal", "boolvar", false) };
ensure(! newbool);
ensure(! LL::CommonControl::get("FakeGlobal", "boolvar").asBoolean());
ensure(LL::CommonControl::set_default("FakeGlobal", "boolvar").asBoolean());
ensure(! LL::CommonControl::toggle("FakeGlobal", "boolvar").asBoolean());
}
template<> template<>
void object::test<8>()
{
set_test_name("CommonControl get_def");
LLSD def{ LL::CommonControl::get_def("FakeGlobal", "strvar") };
ensure_equals(
def,
llsd::map("name", "strvar",
"type", "String",
"value", "woof",
"comment", "string variable"));
}
template<> template<>
void object::test<9>()
{
set_test_name("CommonControl get_groups");
std::vector<std::string> groups{ LL::CommonControl::get_groups() };
ensure_equals(groups.size(), 1);
ensure_equals(groups[0], "FakeGlobal");
}
template<> template<>
void object::test<10>()
{
set_test_name("CommonControl get_vars");
LLSD vars{ LL::CommonControl::get_vars("FakeGlobal") };
// convert from array (unpredictable order) to map
LLSD varsmap{ LLSD::emptyMap() };
for (auto& var : llsd::inArray(vars))
{
varsmap[var["name"].asString()] = var;
}
// comparing maps is order-insensitive
ensure_equals(
varsmap,
llsd::map(
"strvar",
llsd::map("name", "strvar",
"type", "String",
"value", "woof",
"comment", "string variable"),
"boolvar",
llsd::map("name", "boolvar",
"type", "Boolean",
"value", TRUE,
"comment", "bool variable")));
}
} // namespace tut

View File

@ -406,7 +406,7 @@ public:
{
// Per http://confluence.jetbrains.net/display/TCD65/Build+Script+Interaction+with+TeamCity#BuildScriptInteractionwithTeamCity-ServiceMessages
std::string result;
BOOST_FOREACH(char c, str)
for (char c : str)
{
switch (c)
{