summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
authorRick Pasetto <rick@lindenlab.com>2009-12-10 14:21:48 -0800
committerRick Pasetto <rick@lindenlab.com>2009-12-10 14:21:48 -0800
commit248427b8f956f3e879aeda9179b0479a6d778ab7 (patch)
tree068fc5b1858aa4a2af58bbf615a6befea3049c3a /indra
parenta97479f41bae05ff08b3598a9497be9be3ee371d (diff)
PARTIAL DEV-43869: LLMediaDataClient now uses two queues
This is a fairly major change that addresses the issue of an object with constantly-updating media. Before, that object would be put into our single queue and sorted to a particular spot, and since it continuously updates, it would "always be there". That means that nothing "behind" it would ever get serviced. This change introduces two queues for each MDC: one is the same "sorted" queue as before, and the other is unsorted, and "round-robins". New objects go into the sorted queue, objects whose media we already know about get put into the unsorted queue. The two queues are interleaved when serviced (one then the other is serviced -- if one is empty we try the other -- until they are both drained). The round-robin queue works a little differently: after an item is fetched from that queue (remember this would be an item we already know about), that request is marked and put back at the end of the queue. If that object gets a UDP update while in the queue, that mark is "cleared". When it gets to the front of the queue again, if it still marked, it is thrown away. If it is not marked, it is fetched, and again marked and put at the end. This makes the queue self-limiting in how big it can get. I have also made some other changes: - The sorting comparator now just delegates to the object for its "interest" calculation. A higher value = more interesting. LLVOVolume now uses its PixelArea for its "interest" calculation, which seems apparently better (the prior distance calculation was wrong anyway). - The score is cached before the sort operation is performed, so that it won't be expensive to sort - Now, the media version that is fetched is saved in the LLVOVolume, and we do not update if it is not newer (this is not very useful...yet.) - I've introduced hard limits (settable by debug settings) on the size of the queues. The sorted queue will be culled (after sort) to that count. NOTE: this will probably get removed in a later checkin, as I've already gotten feedback that this is not desirable - I've reorganized LLMediaDataClient so it makes more sense. - I've made the request object a little smaller, so the queue won't take up so much memory (more work could be done here) - Added a unit test for the two-queue case (though more tests are needed!)
Diffstat (limited to 'indra')
-rw-r--r--indra/newview/app_settings/settings.xml22
-rwxr-xr-xindra/newview/llmediadataclient.cpp746
-rwxr-xr-xindra/newview/llmediadataclient.h84
-rw-r--r--indra/newview/llvovolume.cpp74
-rw-r--r--indra/newview/llvovolume.h8
-rw-r--r--indra/newview/tests/llmediadataclient_test.cpp208
6 files changed, 720 insertions, 422 deletions
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index c43032a3cf..19efeed7f0 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -5505,6 +5505,28 @@
<key>Value</key>
<real>5.0</real>
</map>
+ <key>PrimMediaMaxSortedQueueSize</key>
+ <map>
+ <key>Comment</key>
+ <string>Maximum number of objects the viewer will load media for initially</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>U32</string>
+ <key>Value</key>
+ <integer>120</integer>
+ </map>
+ <key>PrimMediaMaxRoundRobinQueueSize</key>
+ <map>
+ <key>Comment</key>
+ <string>Maximum number of objects the viewer will continuously update media for</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>U32</string>
+ <key>Value</key>
+ <integer>15</integer>
+ </map>
<key>ProbeHardwareOnStartup</key>
<map>
<key>Comment</key>
diff --git a/indra/newview/llmediadataclient.cpp b/indra/newview/llmediadataclient.cpp
index 3c337961e1..fe2af36302 100755
--- a/indra/newview/llmediadataclient.cpp
+++ b/indra/newview/llmediadataclient.cpp
@@ -5,7 +5,7 @@
* $LicenseInfo:firstyear=2001&license=viewergpl$
*
* Copyright (c) 2001-2009, Linden Research, Inc.
- *
+ * =
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
* to you under the terms of the GNU General Public License, version 2.0
@@ -61,345 +61,271 @@
const F32 LLMediaDataClient::QUEUE_TIMER_DELAY = 1.0; // seconds(s)
const F32 LLMediaDataClient::UNAVAILABLE_RETRY_TIMER_DELAY = 5.0; // secs
const U32 LLMediaDataClient::MAX_RETRIES = 4;
+const U32 LLMediaDataClient::MAX_SORTED_QUEUE_SIZE = 60;
+const U32 LLMediaDataClient::MAX_ROUND_ROBIN_QUEUE_SIZE = 25;
//////////////////////////////////////////////////////////////////////////////////////
//
-// LLMediaDataClient::Request
+// LLMediaDataClient
//
//////////////////////////////////////////////////////////////////////////////////////
-/*static*/U32 LLMediaDataClient::Request::sNum = 0;
-
-LLMediaDataClient::Request::Request(const std::string &cap_name,
- const LLSD& sd_payload,
- LLMediaDataClientObject *obj,
- LLMediaDataClient *mdc)
- : mCapName(cap_name),
- mPayload(sd_payload),
- mObject(obj),
- mNum(++sNum),
- mRetryCount(0),
- mMDC(mdc)
-{
-}
-
-LLMediaDataClient::Request::~Request()
-{
- LL_DEBUGS("LLMediaDataClient") << "~Request" << (*this) << LL_ENDL;
- mMDC = NULL;
- mObject = NULL;
-}
-
-std::string LLMediaDataClient::Request::getCapability() const
+LLMediaDataClient::LLMediaDataClient(F32 queue_timer_delay,
+ F32 retry_timer_delay,
+ U32 max_retries,
+ U32 max_sorted_queue_size,
+ U32 max_round_robin_queue_size)
+ : mQueueTimerDelay(queue_timer_delay),
+ mRetryTimerDelay(retry_timer_delay),
+ mMaxNumRetries(max_retries),
+ mMaxSortedQueueSize(max_sorted_queue_size),
+ mMaxRoundRobinQueueSize(max_round_robin_queue_size),
+ mQueueTimerIsRunning(false),
+ mCurrentQueueIsTheSortedQueue(true)
{
- return getObject()->getCapabilityUrl(getCapName());
}
-// Helper function to get the "type" of request, which just pokes around to
-// discover it.
-LLMediaDataClient::Request::Type LLMediaDataClient::Request::getType() const
+LLMediaDataClient::~LLMediaDataClient()
{
- if (mCapName == "ObjectMediaNavigate")
- {
- return NAVIGATE;
- }
- else if (mCapName == "ObjectMedia")
- {
- const std::string &verb = mPayload["verb"];
- if (verb == "GET")
- {
- return GET;
- }
- else if (verb == "UPDATE")
- {
- return UPDATE;
- }
- }
- llassert(false);
- return GET;
-}
+ stopQueueTimer();
-const char *LLMediaDataClient::Request::getTypeAsString() const
-{
- Type t = getType();
- switch (t)
- {
- case GET:
- return "GET";
- break;
- case UPDATE:
- return "UPDATE";
- break;
- case NAVIGATE:
- return "NAVIGATE";
- break;
- }
- return "";
-}
+ // This should clear the queue, and hopefully call all the destructors.
+ LL_DEBUGS("LLMediaDataClient") << "~LLMediaDataClient destructor: queue: " <<
+ (isEmpty() ? "<empty> " : "<not empty> ") << LL_ENDL;
+ mSortedQueue.clear();
+ mRoundRobinQueue.clear();
+}
-void LLMediaDataClient::Request::reEnqueue() const
+bool LLMediaDataClient::isEmpty() const
{
- // I sure hope this doesn't deref a bad pointer:
- mMDC->enqueue(this);
+ return mSortedQueue.empty() && mRoundRobinQueue.empty();
}
-F32 LLMediaDataClient::Request::getRetryTimerDelay() const
+bool LLMediaDataClient::isInQueue(const LLMediaDataClientObject::ptr_t &object)
{
- return (mMDC == NULL) ? LLMediaDataClient::UNAVAILABLE_RETRY_TIMER_DELAY :
- mMDC->mRetryTimerDelay;
+ return (LLMediaDataClient::findOrRemove(mSortedQueue, object, false/*remove*/, LLMediaDataClient::Request::ANY).notNull()
+ || (LLMediaDataClient::findOrRemove(mRoundRobinQueue, object, false/*remove*/, LLMediaDataClient::Request::ANY).notNull()));
}
-U32 LLMediaDataClient::Request::getMaxNumRetries() const
+bool LLMediaDataClient::removeFromQueue(const LLMediaDataClientObject::ptr_t &object)
{
- return (mMDC == NULL) ? LLMediaDataClient::MAX_RETRIES : mMDC->mMaxNumRetries;
+ bool removedFromSortedQueue = LLMediaDataClient::findOrRemove(mSortedQueue, object, true/*remove*/, LLMediaDataClient::Request::ANY).notNull();
+ bool removedFromRoundRobinQueue = LLMediaDataClient::findOrRemove(mRoundRobinQueue, object, true/*remove*/, LLMediaDataClient::Request::ANY).notNull();
+ return removedFromSortedQueue || removedFromRoundRobinQueue;
}
-std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::Request &r)
+//static
+LLMediaDataClient::request_ptr_t LLMediaDataClient::findOrRemove(request_queue_t &queue, const LLMediaDataClientObject::ptr_t &obj, bool remove, LLMediaDataClient::Request::Type type)
{
- s << "<request>"
- << "<num>" << r.getNum() << "</num>"
- << "<type>" << r.getTypeAsString() << "</type>"
- << "<object_id>" << r.getObject()->getID() << "</object_id>"
- << "<num_retries>" << r.getRetryCount() << "</num_retries>"
- << "</request> ";
- return s;
+ request_ptr_t result;
+ request_queue_t::iterator iter = queue.begin();
+ request_queue_t::iterator end = queue.end();
+ while (iter != end)
+ {
+ if (obj->getID() == (*iter)->getObject()->getID() && (type == LLMediaDataClient::Request::ANY || type == (*iter)->getType()))
+ {
+ result = *iter;
+ if (remove) queue.erase(iter);
+ break;
+ }
+ iter++;
+ }
+ return result;
}
-
-//////////////////////////////////////////////////////////////////////////////////////
-//
-// LLMediaDataClient::Responder::RetryTimer
-//
-//////////////////////////////////////////////////////////////////////////////////////
-
-LLMediaDataClient::Responder::RetryTimer::RetryTimer(F32 time, Responder *mdr)
- : LLEventTimer(time), mResponder(mdr)
+void LLMediaDataClient::request(const LLMediaDataClientObject::ptr_t &object, const LLSD &payload)
{
+ if (object.isNull() || ! object->hasMedia()) return;
+
+ // Push the object on the queue
+ enqueue(new Request(getCapabilityName(), payload, object, this));
}
-// virtual
-LLMediaDataClient::Responder::RetryTimer::~RetryTimer()
+void LLMediaDataClient::enqueue(const Request *request)
{
- LL_DEBUGS("LLMediaDataClient") << "~RetryTimer" << *(mResponder->getRequest()) << LL_ENDL;
-
- // XXX This is weird: Instead of doing the work in tick() (which re-schedules
- // a timer, which might be risky), do it here, in the destructor. Yes, it is very odd.
- // Instead of retrying, we just put the request back onto the queue
- LL_INFOS("LLMediaDataClient") << "RetryTimer fired for: " << *(mResponder->getRequest()) << "retrying" << LL_ENDL;
- mResponder->getRequest()->reEnqueue();
-
- // Release the ref to the responder.
- mResponder = NULL;
+ if (request->isNew())
+ {
+ // Add to sorted queue
+ if (LLMediaDataClient::findOrRemove(mSortedQueue, request->getObject(), true/*remove*/, request->getType()).notNull())
+ {
+ LL_DEBUGS("LLMediaDataClient") << "REMOVING OLD request for " << *request << " ALREADY THERE!" << LL_ENDL;
+ }
+
+ LL_DEBUGS("LLMediaDataClient") << "Queuing SORTED request for " << *request << LL_ENDL;
+
+ // Sadly, we have to const-cast because items put into the queue are not const
+ mSortedQueue.push_back(const_cast<LLMediaDataClient::Request*>(request));
+
+ LL_DEBUGS("LLMediaDataClient") << "SORTED queue:" << mSortedQueue << LL_ENDL;
+ }
+ else {
+ if (mRoundRobinQueue.size() > mMaxRoundRobinQueueSize)
+ {
+ LL_INFOS_ONCE("LLMediaDataClient") << "RR QUEUE MAXED OUT!!!" << LL_ENDL;
+ LL_DEBUGS("LLMediaDataClient") << "Not queuing " << *request << LL_ENDL;
+ return;
+ }
+
+ // ROUND ROBIN: if it is there, and it is a GET request, leave it. If not, put at front!
+ request_ptr_t existing_request;
+ if (request->getType() == Request::GET)
+ {
+ existing_request = LLMediaDataClient::findOrRemove(mRoundRobinQueue, request->getObject(), false/*remove*/, request->getType());
+ }
+ if (existing_request.isNull())
+ {
+ LL_DEBUGS("LLMediaDataClient") << "Queuing RR request for " << *request << LL_ENDL;
+ // Push the request on the pending queue
+ // Sadly, we have to const-cast because items put into the queue are not const
+ mRoundRobinQueue.push_front(const_cast<LLMediaDataClient::Request*>(request));
+
+ LL_DEBUGS("LLMediaDataClient") << "RR queue:" << mRoundRobinQueue << LL_ENDL;
+ }
+ else
+ {
+ LL_DEBUGS("LLMediaDataClient") << "ALREADY THERE: NOT Queuing request for " << *request << LL_ENDL;
+
+ existing_request->markSent(false);
+ }
+ }
+ // Start the timer if not already running
+ startQueueTimer();
}
-// virtual
-BOOL LLMediaDataClient::Responder::RetryTimer::tick()
+void LLMediaDataClient::startQueueTimer()
{
- // Don't fire again
- return TRUE;
+ if (! mQueueTimerIsRunning)
+ {
+ LL_DEBUGS("LLMediaDataClient") << "starting queue timer (delay=" << mQueueTimerDelay << " seconds)" << LL_ENDL;
+ // LLEventTimer automagically takes care of the lifetime of this object
+ new QueueTimer(mQueueTimerDelay, this);
+ }
+ else {
+ LL_DEBUGS("LLMediaDataClient") << "not starting queue timer (it's already running, right???)" << LL_ENDL;
+ }
}
-
-//////////////////////////////////////////////////////////////////////////////////////
-//
-// LLMediaDataClient::Responder
-//
-//////////////////////////////////////////////////////////////////////////////////////
-
-LLMediaDataClient::Responder::Responder(const request_ptr_t &request)
- : mRequest(request)
+void LLMediaDataClient::stopQueueTimer()
{
+ mQueueTimerIsRunning = false;
}
-LLMediaDataClient::Responder::~Responder()
+bool LLMediaDataClient::processQueueTimer()
{
- LL_DEBUGS("LLMediaDataClient") << "~Responder" << *(getRequest()) << LL_ENDL;
- mRequest = NULL;
+ sortQueue();
+
+ if(!isEmpty())
+ {
+ LL_INFOS("LLMediaDataClient") << "QueueTimer::tick() started, SORTED queue size is: " << mSortedQueue.size()
+ << ", RR queue size is: " << mRoundRobinQueue.size() << LL_ENDL;
+ LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() started, SORTED queue is: " << mSortedQueue << LL_ENDL;
+ LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() started, RR queue is: " << mRoundRobinQueue << LL_ENDL;
+ }
+
+ serviceQueue();
+
+ LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() finished, SORTED queue size is: " << mSortedQueue.size()
+ << ", RR queue size is: " << mRoundRobinQueue.size() << LL_ENDL;
+ LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() finished, SORTED queue is: " << mSortedQueue << LL_ENDL;
+ LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() finished, RR queue is: " << mRoundRobinQueue << LL_ENDL;
+
+ return isEmpty();
}
-/*virtual*/
-void LLMediaDataClient::Responder::error(U32 status, const std::string& reason)
+void LLMediaDataClient::sortQueue()
{
- if (status == HTTP_SERVICE_UNAVAILABLE)
+ if(!mSortedQueue.empty())
{
- F32 retry_timeout = mRequest->getRetryTimerDelay();
-
- mRequest->incRetryCount();
-
- if (mRequest->getRetryCount() < mRequest->getMaxNumRetries())
+ // Score all items first
+ request_queue_t::iterator iter = mSortedQueue.begin();
+ request_queue_t::iterator end = mSortedQueue.end();
+ while (iter != end)
{
- LL_INFOS("LLMediaDataClient") << *mRequest << "got SERVICE_UNAVAILABLE...retrying in " << retry_timeout << " seconds" << LL_ENDL;
-
- // Start timer (instances are automagically tracked by
- // InstanceTracker<> and LLEventTimer)
- new RetryTimer(F32(retry_timeout/*secs*/), this);
+ (*iter)->updateScore();
+ iter++;
}
- else {
- LL_INFOS("LLMediaDataClient") << *mRequest << "got SERVICE_UNAVAILABLE...retry count " <<
- mRequest->getRetryCount() << " exceeds " << mRequest->getMaxNumRetries() << ", not retrying" << LL_ENDL;
+
+ // Re-sort the list...
+ // NOTE: should this be a stable_sort? If so we need to change to using a vector.
+ mSortedQueue.sort(LLMediaDataClient::compareRequests);
+
+ // ...then cull items over the max
+ U32 size = mSortedQueue.size();
+ if (size > mMaxSortedQueueSize)
+ {
+ U32 num_to_cull = (size - mMaxSortedQueueSize);
+ LL_INFOS("LLMediaDataClient") << "sorted queue MAXED OUT! Culling "
+ << num_to_cull << " items" << LL_ENDL;
+ while (num_to_cull-- > 0)
+ {
+ mSortedQueue.pop_back();
+ }
}
}
- else {
- std::string msg = boost::lexical_cast<std::string>(status) + ": " + reason;
- LL_WARNS("LLMediaDataClient") << *mRequest << " http error(" << msg << ")" << LL_ENDL;
- }
-}
-
-
-/*virtual*/
-void LLMediaDataClient::Responder::result(const LLSD& content)
-{
- LL_INFOS("LLMediaDataClient") << *mRequest << "result : " << ll_print_sd(content) << LL_ENDL;
}
-
-//////////////////////////////////////////////////////////////////////////////////////
-//
-// LLMediaDataClient::Comparator
-//
-//////////////////////////////////////////////////////////////////////////////////////
-
// static
bool LLMediaDataClient::compareRequests(const request_ptr_t &o1, const request_ptr_t &o2)
{
if (o2.isNull()) return true;
if (o1.isNull()) return false;
-
- // The score is intended to be a measure of how close an object is or
- // how much screen real estate (interest) it takes up
- // Further away = lower score.
- // Lesser interest = lower score
- // For instance, here are some cases:
- // 1: Two items with no impl, closest one wins
- // 2: Two items with an impl: interest should rule, but distance is
- // still taken into account (i.e. something really close might take
- // precedence over a large item far away)
- // 3: One item with an impl, another without: item with impl wins
- // (XXX is that what we want?)
- // Calculate the scores for each.
- F64 o1_score = getObjectScore(o1->getObject());
- F64 o2_score = getObjectScore(o2->getObject());
- return ( o1_score > o2_score );
+ return ( o1->getScore() > o2->getScore() );
}
-// static
-F64 LLMediaDataClient::getObjectScore(const LLMediaDataClientObject::ptr_t &obj)
-{
- // *TODO: make this less expensive?
- F64 dist = obj->getDistanceFromAvatar() + 0.1; // avoids div by 0
- // square the distance so that they are in the same "unit magnitude" as
- // the interest (which is an area)
- dist *= dist;
- F64 interest = obj->getTotalMediaInterest() + 1.0;
-
- return interest/dist;
-}
-
-//////////////////////////////////////////////////////////////////////////////////////
-//
-// LLMediaDataClient::PriorityQueue
-// Queue of LLMediaDataClientObject smart pointers to request media for.
-//
-//////////////////////////////////////////////////////////////////////////////////////
-
-// dump the queue
-std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::request_queue_t &q)
+void LLMediaDataClient::swapCurrentQueue()
{
- int i = 0;
- LLMediaDataClient::request_queue_t::const_iterator iter = q.begin();
- LLMediaDataClient::request_queue_t::const_iterator end = q.end();
- while (iter != end)
+ // Swap
+ mCurrentQueueIsTheSortedQueue = !mCurrentQueueIsTheSortedQueue;
+ // If its empty, swap back
+ if (getCurrentQueue()->empty())
{
- s << "\t" << i << "]: " << (*iter)->getObject()->getID().asString();
- iter++;
- i++;
+ mCurrentQueueIsTheSortedQueue = !mCurrentQueueIsTheSortedQueue;
}
- return s;
}
-// find the given object in the queue.
-bool LLMediaDataClient::find(const LLMediaDataClientObject::ptr_t &obj) const
+LLMediaDataClient::request_queue_t *LLMediaDataClient::getCurrentQueue()
{
- request_queue_t::const_iterator iter = pRequestQueue->begin();
- request_queue_t::const_iterator end = pRequestQueue->end();
- while (iter != end)
- {
- if (obj->getID() == (*iter)->getObject()->getID())
- {
- return true;
- }
- iter++;
- }
- return false;
-}
-
-//////////////////////////////////////////////////////////////////////////////////////
-//
-// LLMediaDataClient::QueueTimer
-// Queue of LLMediaDataClientObject smart pointers to request media for.
-//
-//////////////////////////////////////////////////////////////////////////////////////
-
-LLMediaDataClient::QueueTimer::QueueTimer(F32 time, LLMediaDataClient *mdc)
- : LLEventTimer(time), mMDC(mdc)
-{
- mMDC->setIsRunning(true);
-}
-
-LLMediaDataClient::QueueTimer::~QueueTimer()
-{
- LL_DEBUGS("LLMediaDataClient") << "~QueueTimer" << LL_ENDL;
- mMDC->setIsRunning(false);
- mMDC = NULL;
+ return (mCurrentQueueIsTheSortedQueue) ? &mSortedQueue : &mRoundRobinQueue;
}
-// virtual
-BOOL LLMediaDataClient::QueueTimer::tick()
-{
- if (NULL == mMDC->pRequestQueue)
- {
- // Shutting down? stop.
- LL_DEBUGS("LLMediaDataClient") << "queue gone" << LL_ENDL;
- return TRUE;
- }
-
- request_queue_t &queue = *(mMDC->pRequestQueue);
-
- if(!queue.empty())
- {
- LL_INFOS("LLMediaDataClient") << "QueueTimer::tick() started, queue is: " << queue << LL_ENDL;
-
- // Re-sort the list every time...
- // XXX Is this really what we want?
- queue.sort(LLMediaDataClient::compareRequests);
- }
+void LLMediaDataClient::serviceQueue()
+{
+ request_queue_t *queue_p = getCurrentQueue();
// quick retry loop for cases where we shouldn't wait for the next timer tick
while(true)
{
- if (queue.empty())
+ if (queue_p->empty())
{
- LL_DEBUGS("LLMediaDataClient") << "queue empty: " << queue << LL_ENDL;
- return TRUE;
+ LL_DEBUGS("LLMediaDataClient") << "queue empty: " << (*queue_p) << LL_ENDL;
+ break;
}
-
+
// Peel one off of the items from the queue, and execute request
- request_ptr_t request = queue.front();
+ request_ptr_t request = queue_p->front();
llassert(!request.isNull());
const LLMediaDataClientObject *object = (request.isNull()) ? NULL : request->getObject();
bool performed_request = false;
bool error = false;
llassert(NULL != object);
-
+
+ if(request->isMarkedSent())
+ {
+ // This object has been sent and not re-requested. Skip it.
+ LL_INFOS("LLMediaDataClient") << "Skipping " << *request << ": object is marked sent!" << LL_ENDL;
+ queue_p->pop_front();
+ continue; // jump back to the start of the quick retry loop
+ }
+
if(object->isDead())
{
// This object has been marked dead. Pop it and move on to the next item in the queue immediately.
LL_INFOS("LLMediaDataClient") << "Skipping " << *request << ": object is dead!" << LL_ENDL;
- queue.pop_front();
+ queue_p->pop_front();
continue; // jump back to the start of the quick retry loop
}
-
+
if (NULL != object && object->hasMedia())
{
std::string url = request->getCapability();
@@ -407,9 +333,9 @@ BOOL LLMediaDataClient::QueueTimer::tick()
{
const LLSD &sd_payload = request->getPayload();
LL_INFOS("LLMediaDataClient") << "Sending request for " << *request << LL_ENDL;
-
+
// Call the subclass for creating the responder
- LLHTTPClient::post(url, sd_payload, mMDC->createResponder(request));
+ LLHTTPClient::post(url, sd_payload, createResponder(request));
performed_request = true;
}
else {
@@ -431,16 +357,24 @@ BOOL LLMediaDataClient::QueueTimer::tick()
}
error = true;
}
- bool exceeded_retries = request->getRetryCount() > mMDC->mMaxNumRetries;
+ bool exceeded_retries = request->getRetryCount() > mMaxNumRetries;
if (performed_request || exceeded_retries || error) // Try N times before giving up
{
if (exceeded_retries)
{
LL_WARNS("LLMediaDataClient") << "Could not send request " << *request << " for "
- << mMDC->mMaxNumRetries << " tries...popping object id " << object->getID() << LL_ENDL;
+ << mMaxNumRetries << " tries...popping object id " << object->getID() << LL_ENDL;
// XXX Should we bring up a warning dialog??
}
- queue.pop_front();
+
+ queue_p->pop_front();
+
+ if (! mCurrentQueueIsTheSortedQueue) {
+ // Round robin
+ request->markSent(true);
+ mRoundRobinQueue.push_back(request);
+ }
+ if (error) continue;
}
else {
request->incRetryCount();
@@ -448,86 +382,267 @@ BOOL LLMediaDataClient::QueueTimer::tick()
// end of quick retry loop -- any cases where we want to loop will use 'continue' to jump back to the start.
break;
- }
+ }
+
+ swapCurrentQueue();
+}
+
+// dump the queue
+std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::request_queue_t &q)
+{
+ int i = 0;
+ LLMediaDataClient::request_queue_t::const_iterator iter = q.begin();
+ LLMediaDataClient::request_queue_t::const_iterator end = q.end();
+ while (iter != end)
+ {
+ s << "\t" << i << "]: " << (*iter)->getObject()->getID().asString();
+ iter++;
+ i++;
+ }
+ return s;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////
+//
+// LLMediaDataClient::QueueTimer
+// Queue of LLMediaDataClientObject smart pointers to request media for.
+//
+//////////////////////////////////////////////////////////////////////////////////////
+
+LLMediaDataClient::QueueTimer::QueueTimer(F32 time, LLMediaDataClient *mdc)
+: LLEventTimer(time), mMDC(mdc)
+{
+ mMDC->setIsRunning(true);
+}
+
+LLMediaDataClient::QueueTimer::~QueueTimer()
+{
+ LL_DEBUGS("LLMediaDataClient") << "~QueueTimer" << LL_ENDL;
+ mMDC->setIsRunning(false);
+ mMDC = NULL;
+}
- LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() finished, queue is now: " << (*(mMDC->pRequestQueue)) << LL_ENDL;
+// virtual
+BOOL LLMediaDataClient::QueueTimer::tick()
+{
+ if (mMDC.isNull()) return TRUE;
+ return mMDC->processQueueTimer();
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////
+//
+// LLMediaDataClient::Responder::RetryTimer
+//
+//////////////////////////////////////////////////////////////////////////////////////
- return queue.empty();
+LLMediaDataClient::Responder::RetryTimer::RetryTimer(F32 time, Responder *mdr)
+: LLEventTimer(time), mResponder(mdr)
+{
}
+
+// virtual
+LLMediaDataClient::Responder::RetryTimer::~RetryTimer()
+{
+ LL_DEBUGS("LLMediaDataClient") << "~RetryTimer" << *(mResponder->getRequest()) << LL_ENDL;
-void LLMediaDataClient::startQueueTimer()
+ // XXX This is weird: Instead of doing the work in tick() (which re-schedules
+ // a timer, which might be risky), do it here, in the destructor. Yes, it is very odd.
+ // Instead of retrying, we just put the request back onto the queue
+ LL_INFOS("LLMediaDataClient") << "RetryTimer fired for: " << *(mResponder->getRequest()) << " retrying" << LL_ENDL;
+ mResponder->getRequest()->reEnqueue();
+
+ // Release the ref to the responder.
+ mResponder = NULL;
+}
+
+// virtual
+BOOL LLMediaDataClient::Responder::RetryTimer::tick()
{
- if (! mQueueTimerIsRunning)
+ // Don't fire again
+ return TRUE;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////
+//
+// LLMediaDataClient::Request
+//
+//////////////////////////////////////////////////////////////////////////////////////
+/*static*/U32 LLMediaDataClient::Request::sNum = 0;
+
+LLMediaDataClient::Request::Request(const char *cap_name,
+ const LLSD& sd_payload,
+ LLMediaDataClientObject *obj,
+ LLMediaDataClient *mdc)
+: mCapName(cap_name),
+ mPayload(sd_payload),
+ mObject(obj),
+ mNum(++sNum),
+ mRetryCount(0),
+ mMDC(mdc),
+ mMarkedSent(false),
+ mScore((F64)0.0)
+{
+}
+
+LLMediaDataClient::Request::~Request()
+{
+ LL_DEBUGS("LLMediaDataClient") << "~Request" << (*this) << LL_ENDL;
+ mMDC = NULL;
+ mObject = NULL;
+}
+
+
+std::string LLMediaDataClient::Request::getCapability() const
+{
+ return getObject()->getCapabilityUrl(getCapName());
+}
+
+// Helper function to get the "type" of request, which just pokes around to
+// discover it.
+LLMediaDataClient::Request::Type LLMediaDataClient::Request::getType() const
+{
+ if (0 == strcmp(mCapName, "ObjectMediaNavigate"))
{
- LL_INFOS("LLMediaDataClient") << "starting queue timer (delay=" << mQueueTimerDelay << " seconds)" << LL_ENDL;
- // LLEventTimer automagically takes care of the lifetime of this object
- new QueueTimer(mQueueTimerDelay, this);
+ return NAVIGATE;
}
- else {
- LL_DEBUGS("LLMediaDataClient") << "not starting queue timer (it's already running, right???)" << LL_ENDL;
+ else if (0 == strcmp(mCapName, "ObjectMedia"))
+ {
+ const std::string &verb = mPayload["verb"];
+ if (verb == "GET")
+ {
+ return GET;
+ }
+ else if (verb == "UPDATE")
+ {
+ return UPDATE;
+ }
}
+ llassert(false);
+ return GET;
}
-
-void LLMediaDataClient::stopQueueTimer()
+
+const char *LLMediaDataClient::Request::getTypeAsString() const
{
- mQueueTimerIsRunning = false;
+ Type t = getType();
+ switch (t)
+ {
+ case GET:
+ return "GET";
+ break;
+ case UPDATE:
+ return "UPDATE";
+ break;
+ case NAVIGATE:
+ return "NAVIGATE";
+ break;
+ case ANY:
+ return "ANY";
+ break;
+ }
+ return "";
}
-
-void LLMediaDataClient::request(const LLMediaDataClientObject::ptr_t &object, const LLSD &payload)
+
+
+void LLMediaDataClient::Request::reEnqueue() const
{
- if (object.isNull() || ! object->hasMedia()) return;
+ // I sure hope this doesn't deref a bad pointer:
+ mMDC->enqueue(this);
+}
- // Push the object on the priority queue
- enqueue(new Request(getCapabilityName(), payload, object, this));
+F32 LLMediaDataClient::Request::getRetryTimerDelay() const
+{
+ return (mMDC == NULL) ? LLMediaDataClient::UNAVAILABLE_RETRY_TIMER_DELAY :
+ mMDC->mRetryTimerDelay;
}
-void LLMediaDataClient::enqueue(const Request *request)
+U32 LLMediaDataClient::Request::getMaxNumRetries() const
{
- LL_INFOS("LLMediaDataClient") << "Queuing request for " << *request << LL_ENDL;
- // Push the request on the priority queue
- // Sadly, we have to const-cast because items put into the queue are not const
- pRequestQueue->push_back(const_cast<LLMediaDataClient::Request*>(request));
- LL_DEBUGS("LLMediaDataClient") << "Queue:" << (*pRequestQueue) << LL_ENDL;
- // Start the timer if not already running
- startQueueTimer();
+ return (mMDC == NULL) ? LLMediaDataClient::MAX_RETRIES : mMDC->mMaxNumRetries;
+}
+
+void LLMediaDataClient::Request::markSent(bool flag)
+{
+ if (mMarkedSent != flag)
+ {
+ mMarkedSent = flag;
+ if (!mMarkedSent)
+ {
+ mNum = ++sNum;
+ }
+ }
+}
+
+void LLMediaDataClient::Request::updateScore()
+{
+ F64 tmp = mObject->getMediaInterest();
+ if (tmp != mScore)
+ {
+ LL_DEBUGS("LLMediaDataClient") << "Score for " << mObject->getID() << " changed from " << mScore << " to " << tmp << LL_ENDL;
+ mScore = tmp;
+ }
+}
+
+std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::Request &r)
+{
+ s << "request: num=" << r.getNum()
+ << " type=" << r.getTypeAsString()
+ << " ID=" << r.getObject()->getID()
+ << " #retries=" << r.getRetryCount();
+ return s;
}
+
//////////////////////////////////////////////////////////////////////////////////////
//
-// LLMediaDataClient
+// LLMediaDataClient::Responder
//
//////////////////////////////////////////////////////////////////////////////////////
-LLMediaDataClient::LLMediaDataClient(F32 queue_timer_delay,
- F32 retry_timer_delay,
- U32 max_retries)
- : mQueueTimerDelay(queue_timer_delay),
- mRetryTimerDelay(retry_timer_delay),
- mMaxNumRetries(max_retries),
- mQueueTimerIsRunning(false)
+LLMediaDataClient::Responder::Responder(const request_ptr_t &request)
+: mRequest(request)
{
- pRequestQueue = new request_queue_t();
}
-LLMediaDataClient::~LLMediaDataClient()
+LLMediaDataClient::Responder::~Responder()
{
- stopQueueTimer();
-
- // This should clear the queue, and hopefully call all the destructors.
- LL_DEBUGS("LLMediaDataClient") << "~LLMediaDataClient destructor: queue: " <<
- (pRequestQueue->empty() ? "<empty> " : "<not empty> ") << (*pRequestQueue) << LL_ENDL;
- delete pRequestQueue;
- pRequestQueue = NULL;
+ LL_DEBUGS("LLMediaDataClient") << "~Responder" << *(getRequest()) << LL_ENDL;
+ mRequest = NULL;
}
-bool LLMediaDataClient::isEmpty() const
+/*virtual*/
+void LLMediaDataClient::Responder::error(U32 status, const std::string& reason)
{
- return (NULL == pRequestQueue) ? true : pRequestQueue->empty();
+ if (status == HTTP_SERVICE_UNAVAILABLE)
+ {
+ F32 retry_timeout = mRequest->getRetryTimerDelay();
+
+ mRequest->incRetryCount();
+
+ if (mRequest->getRetryCount() < mRequest->getMaxNumRetries())
+ {
+ LL_INFOS("LLMediaDataClient") << *mRequest << " got SERVICE_UNAVAILABLE...retrying in " << retry_timeout << " seconds" << LL_ENDL;
+
+ // Start timer (instances are automagically tracked by
+ // InstanceTracker<> and LLEventTimer)
+ new RetryTimer(F32(retry_timeout/*secs*/), this);
+ }
+ else {
+ LL_INFOS("LLMediaDataClient") << *mRequest << " got SERVICE_UNAVAILABLE...retry count " <<
+ mRequest->getRetryCount() << " exceeds " << mRequest->getMaxNumRetries() << ", not retrying" << LL_ENDL;
+ }
+ }
+ else {
+ std::string msg = boost::lexical_cast<std::string>(status) + ": " + reason;
+ LL_WARNS("LLMediaDataClient") << *mRequest << " http error(" << msg << ")" << LL_ENDL;
+ }
}
-bool LLMediaDataClient::isInQueue(const LLMediaDataClientObject::ptr_t &object) const
+/*virtual*/
+void LLMediaDataClient::Responder::result(const LLSD& content)
{
- return (NULL == pRequestQueue) ? false : find(object);
+ LL_DEBUGS("LLMediaDataClient") << *mRequest << " result : " << ll_print_sd(content) << LL_ENDL;
}
//////////////////////////////////////////////////////////////////////////////////////
@@ -569,7 +684,7 @@ void LLObjectMediaDataClient::updateMedia(LLMediaDataClientObject *object)
}
sd_payload[LLTextureEntry::OBJECT_MEDIA_DATA_KEY] = object_media_data;
- LL_INFOS("LLMediaDataClient") << "update media data: " << object->getID() << " " << ll_print_sd(sd_payload) << LL_ENDL;
+ LL_DEBUGS("LLMediaDataClient") << "update media data: " << object->getID() << " " << ll_print_sd(sd_payload) << LL_ENDL;
request(object, sd_payload);
}
@@ -581,7 +696,7 @@ void LLObjectMediaDataClient::Responder::result(const LLSD& content)
llassert(type == LLMediaDataClient::Request::GET || type == LLMediaDataClient::Request::UPDATE)
if (type == LLMediaDataClient::Request::GET)
{
- LL_INFOS("LLMediaDataClient") << *(getRequest()) << "GET returned: " << ll_print_sd(content) << LL_ENDL;
+ LL_DEBUGS("LLMediaDataClient") << *(getRequest()) << " GET returned: " << ll_print_sd(content) << LL_ENDL;
// Look for an error
if (content.has("error"))
@@ -598,12 +713,13 @@ void LLObjectMediaDataClient::Responder::result(const LLSD& content)
if (object_id != getRequest()->getObject()->getID())
{
// NOT good, wrong object id!!
- LL_WARNS("LLMediaDataClient") << *(getRequest()) << "DROPPING response with wrong object id (" << object_id << ")" << LL_ENDL;
+ LL_WARNS("LLMediaDataClient") << *(getRequest()) << " DROPPING response with wrong object id (" << object_id << ")" << LL_ENDL;
return;
}
// Otherwise, update with object media data
- getRequest()->getObject()->updateObjectMediaData(content[LLTextureEntry::OBJECT_MEDIA_DATA_KEY]);
+ getRequest()->getObject()->updateObjectMediaData(content[LLTextureEntry::OBJECT_MEDIA_DATA_KEY],
+ content[LLTextureEntry::MEDIA_VERSION_KEY]);
}
}
else if (type == LLMediaDataClient::Request::UPDATE)
diff --git a/indra/newview/llmediadataclient.h b/indra/newview/llmediadataclient.h
index 812e9cbdec..ffb8a4632b 100755
--- a/indra/newview/llmediadataclient.h
+++ b/indra/newview/llmediadataclient.h
@@ -55,15 +55,17 @@ public:
// Does this object have media?
virtual bool hasMedia() const = 0;
// Update the object's media data to the given array
- virtual void updateObjectMediaData(LLSD const &media_data_array) = 0;
- // Return the distance from the object to the avatar
- virtual F64 getDistanceFromAvatar() const = 0;
+ virtual void updateObjectMediaData(LLSD const &media_data_array, const std::string &version_string) = 0;
// Return the total "interest" of the media (on-screen area)
- virtual F64 getTotalMediaInterest() const = 0;
+ virtual F64 getMediaInterest() const = 0;
// Return the given cap url
virtual std::string getCapabilityUrl(const std::string &name) const = 0;
// Return whether the object has been marked dead
virtual bool isDead() const = 0;
+ // Returns a media version number for the object
+ virtual U32 getMediaVersion() const = 0;
+ // Returns whether we've seen this object yet or not
+ virtual bool isNew() const = 0;
// smart pointer
typedef LLPointer<LLMediaDataClientObject> ptr_t;
@@ -79,11 +81,15 @@ public:
const static F32 QUEUE_TIMER_DELAY;// = 1.0; // seconds(s)
const static F32 UNAVAILABLE_RETRY_TIMER_DELAY;// = 5.0; // secs
const static U32 MAX_RETRIES;// = 4;
+ const static U32 MAX_SORTED_QUEUE_SIZE;// = 60;
+ const static U32 MAX_ROUND_ROBIN_QUEUE_SIZE;// = 25;
// Constructor
LLMediaDataClient(F32 queue_timer_delay = QUEUE_TIMER_DELAY,
F32 retry_timer_delay = UNAVAILABLE_RETRY_TIMER_DELAY,
- U32 max_retries = MAX_RETRIES);
+ U32 max_retries = MAX_RETRIES,
+ U32 max_sorted_queue_size = MAX_SORTED_QUEUE_SIZE,
+ U32 max_round_robin_queue_size = MAX_ROUND_ROBIN_QUEUE_SIZE);
// Make the request
void request(const LLMediaDataClientObject::ptr_t &object, const LLSD &payload);
@@ -94,7 +100,13 @@ public:
bool isEmpty() const;
// Returns true iff the given object is in the queue
- bool isInQueue(const LLMediaDataClientObject::ptr_t &object) const;
+ bool isInQueue(const LLMediaDataClientObject::ptr_t &object);
+
+ // Remove the given object from the queue. Returns true iff the given object is removed.
+ bool removeFromQueue(const LLMediaDataClientObject::ptr_t &object);
+
+ // Called only by the Queue timer and tests (potentially)
+ bool processQueueTimer();
protected:
// Destructor
@@ -107,11 +119,12 @@ protected:
enum Type {
GET,
UPDATE,
- NAVIGATE
+ NAVIGATE,
+ ANY
};
- Request(const std::string &cap_name, const LLSD& sd_payload, LLMediaDataClientObject *obj, LLMediaDataClient *mdc);
- const std::string &getCapName() const { return mCapName; }
+ Request(const char *cap_name, const LLSD& sd_payload, LLMediaDataClientObject *obj, LLMediaDataClient *mdc);
+ const char *getCapName() const { return mCapName; }
const LLSD &getPayload() const { return mPayload; }
LLMediaDataClientObject *getObject() const { return mObject; }
@@ -132,6 +145,12 @@ protected:
F32 getRetryTimerDelay() const;
U32 getMaxNumRetries() const;
+ bool isNew() const { return mObject.notNull() ? mObject->isNew() : false; }
+ void markSent(bool flag);
+ bool isMarkedSent() const { return mMarkedSent; }
+ void updateScore();
+ F64 getScore() const { return mScore; }
+
public:
friend std::ostream& operator<<(std::ostream &s, const Request &q);
@@ -139,14 +158,16 @@ protected:
virtual ~Request(); // use unref();
private:
- std::string mCapName;
+ const char *mCapName;
LLSD mPayload;
LLMediaDataClientObject::ptr_t mObject;
// Simple tracking
- const U32 mNum;
+ U32 mNum;
static U32 sNum;
U32 mRetryCount;
-
+ F64 mScore;
+ bool mMarkedSent;
+
// Back pointer to the MDC...not a ref!
LLMediaDataClient *mMDC;
};
@@ -184,18 +205,23 @@ protected:
};
protected:
-
- void enqueue(const Request*);
-
+
// Subclasses must override this factory method to return a new responder
virtual Responder *createResponder(const request_ptr_t &request) const = 0;
// Subclasses must override to return a cap name
virtual const char *getCapabilityName() const = 0;
-private:
+ virtual void sortQueue();
+ virtual void serviceQueue();
+private:
typedef std::list<request_ptr_t> request_queue_t;
+
+ void enqueue(const Request*);
+
+ // Return whether the given object is/was in the queue
+ static LLMediaDataClient::request_ptr_t findOrRemove(request_queue_t &queue, const LLMediaDataClientObject::ptr_t &obj, bool remove, Request::Type type);
// Comparator for sorting
static bool compareRequests(const request_ptr_t &o1, const request_ptr_t &o2);
@@ -215,31 +241,37 @@ private:
// back-pointer
LLPointer<LLMediaDataClient> mMDC;
};
-
- // Return whether the given object is in the queue
- bool find(const LLMediaDataClientObject::ptr_t &obj) const;
void startQueueTimer();
void stopQueueTimer();
void setIsRunning(bool val) { mQueueTimerIsRunning = val; }
-
+
+ void swapCurrentQueue();
+ request_queue_t *getCurrentQueue();
+
const F32 mQueueTimerDelay;
const F32 mRetryTimerDelay;
const U32 mMaxNumRetries;
+ const U32 mMaxSortedQueueSize;
+ const U32 mMaxRoundRobinQueueSize;
bool mQueueTimerIsRunning;
- request_queue_t *pRequestQueue;
+ request_queue_t mSortedQueue;
+ request_queue_t mRoundRobinQueue;
+ bool mCurrentQueueIsTheSortedQueue;
};
-// MediaDataResponder specific for the ObjectMedia cap
+// MediaDataClient specific for the ObjectMedia cap
class LLObjectMediaDataClient : public LLMediaDataClient
{
public:
LLObjectMediaDataClient(F32 queue_timer_delay = QUEUE_TIMER_DELAY,
F32 retry_timer_delay = UNAVAILABLE_RETRY_TIMER_DELAY,
- U32 max_retries = MAX_RETRIES)
+ U32 max_retries = MAX_RETRIES,
+ U32 max_sorted_queue_size = MAX_SORTED_QUEUE_SIZE,
+ U32 max_round_robin_queue_size = MAX_ROUND_ROBIN_QUEUE_SIZE)
: LLMediaDataClient(queue_timer_delay, retry_timer_delay, max_retries)
{}
~LLObjectMediaDataClient() {}
@@ -264,7 +296,7 @@ protected:
};
-// MediaDataResponder specific for the ObjectMediaNavigate cap
+// MediaDataClient specific for the ObjectMediaNavigate cap
class LLObjectMediaNavigateClient : public LLMediaDataClient
{
public:
@@ -273,7 +305,9 @@ public:
LLObjectMediaNavigateClient(F32 queue_timer_delay = QUEUE_TIMER_DELAY,
F32 retry_timer_delay = UNAVAILABLE_RETRY_TIMER_DELAY,
- U32 max_retries = MAX_RETRIES)
+ U32 max_retries = MAX_RETRIES,
+ U32 max_sorted_queue_size = MAX_SORTED_QUEUE_SIZE,
+ U32 max_round_robin_queue_size = MAX_ROUND_ROBIN_QUEUE_SIZE)
: LLMediaDataClient(queue_timer_delay, retry_timer_delay, max_retries)
{}
~LLObjectMediaNavigateClient() {}
diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp
index cf61994fea..ffadece615 100644
--- a/indra/newview/llvovolume.cpp
+++ b/indra/newview/llvovolume.cpp
@@ -92,7 +92,7 @@ static LLFastTimer::DeclareTimer FTM_GEN_VOLUME("Generate Volumes");
class LLMediaDataClientObjectImpl : public LLMediaDataClientObject
{
public:
- LLMediaDataClientObjectImpl(LLVOVolume *obj) : mObject(obj) {}
+ LLMediaDataClientObjectImpl(LLVOVolume *obj, bool isNew) : mObject(obj), mNew(isNew) {}
LLMediaDataClientObjectImpl() { mObject = NULL; }
virtual U8 getMediaDataCount() const
@@ -128,14 +128,14 @@ public:
virtual bool hasMedia() const
{ return mObject->hasMedia(); }
- virtual void updateObjectMediaData(LLSD const &data)
- { mObject->updateObjectMediaData(data); }
-
- virtual F64 getDistanceFromAvatar() const
- { return mObject->getRenderPosition().length(); }
+ virtual void updateObjectMediaData(LLSD const &data, const std::string &version_string)
+ { mObject->updateObjectMediaData(data, version_string); }
- virtual F64 getTotalMediaInterest() const
- { return mObject->getTotalMediaInterest(); }
+ virtual F64 getMediaInterest() const
+ {
+ F64 tmp = mObject->getTotalMediaInterest();
+ return (tmp < 0.0) ? mObject->getPixelArea() : tmp;
+ }
virtual std::string getCapabilityUrl(const std::string &name) const
{ return mObject->getRegion()->getCapability(name); }
@@ -143,8 +143,15 @@ public:
virtual bool isDead() const
{ return mObject->isDead(); }
+ virtual U32 getMediaVersion() const
+ { return LLTextureEntry::getVersionFromMediaVersionString(mObject->getMediaURL()); }
+
+ virtual bool isNew() const
+ { return mNew; }
+
private:
LLPointer<LLVOVolume> mObject;
+ bool mNew;
};
@@ -165,6 +172,7 @@ LLVOVolume::LLVOVolume(const LLUUID &id, const LLPCode pcode, LLViewerRegion *re
mSpotLightPriority = 0.f;
mMediaImplList.resize(getNumTEs());
+ mLastFetchedMediaVersion = -1;
}
LLVOVolume::~LLVOVolume()
@@ -190,7 +198,9 @@ void LLVOVolume::markDead()
{
if (!mDead)
{
- // TODO: tell LLMediaDataClient to remove this object from its queue
+ LLMediaDataClientObject::ptr_t obj = new LLMediaDataClientObjectImpl(const_cast<LLVOVolume*>(this), false);
+ sObjectMediaClient->removeFromQueue(obj);
+ sObjectMediaNavigateClient->removeFromQueue(obj);
// Detach all media impls from this object
for(U32 i = 0 ; i < mMediaImplList.size() ; i++)
@@ -210,8 +220,12 @@ void LLVOVolume::initClass()
const F32 queue_timer_delay = gSavedSettings.getF32("PrimMediaRequestQueueDelay");
const F32 retry_timer_delay = gSavedSettings.getF32("PrimMediaRetryTimerDelay");
const U32 max_retries = gSavedSettings.getU32("PrimMediaMaxRetries");
- sObjectMediaClient = new LLObjectMediaDataClient(queue_timer_delay, retry_timer_delay, max_retries);
- sObjectMediaNavigateClient = new LLObjectMediaNavigateClient(queue_timer_delay, retry_timer_delay, max_retries);
+ const U32 max_sorted_queue_size = gSavedSettings.getU32("PrimMediaMaxSortedQueueSize");
+ const U32 max_round_robin_queue_size = gSavedSettings.getU32("PrimMediaMaxRoundRobinQueueSize");
+ sObjectMediaClient = new LLObjectMediaDataClient(queue_timer_delay, retry_timer_delay, max_retries,
+ max_sorted_queue_size, max_round_robin_queue_size);
+ sObjectMediaNavigateClient = new LLObjectMediaNavigateClient(queue_timer_delay, retry_timer_delay,
+ max_retries, max_sorted_queue_size, max_round_robin_queue_size);
}
// static
@@ -406,7 +420,7 @@ U32 LLVOVolume::processUpdateMessage(LLMessageSystem *mesgsys,
// If the media changed at all, request new media data
LL_DEBUGS("MediaOnAPrim") << "Media update: " << getID() << ": retval=" << retval << " Media URL: " <<
((mMedia) ? mMedia->mMediaURL : std::string("")) << LL_ENDL;
- requestMediaDataUpdate();
+ requestMediaDataUpdate(retval & MEDIA_FLAGS_CHANGED);
}
else {
LL_INFOS("MediaOnAPrim") << "Ignoring media update for: " << getID() << " Media URL: " <<
@@ -1698,16 +1712,16 @@ LLVector3 LLVOVolume::getApproximateFaceNormal(U8 face_id)
return result;
}
-void LLVOVolume::requestMediaDataUpdate()
+void LLVOVolume::requestMediaDataUpdate(bool isNew)
{
- sObjectMediaClient->fetchMedia(new LLMediaDataClientObjectImpl(this));
+ sObjectMediaClient->fetchMedia(new LLMediaDataClientObjectImpl(this, isNew));
}
bool LLVOVolume::isMediaDataBeingFetched() const
{
// I know what I'm doing by const_casting this away: this is just
// a wrapper class that is only going to do a lookup.
- return sObjectMediaClient->isInQueue(new LLMediaDataClientObjectImpl(const_cast<LLVOVolume*>(this)));
+ return sObjectMediaClient->isInQueue(new LLMediaDataClientObjectImpl(const_cast<LLVOVolume*>(this), false));
}
void LLVOVolume::cleanUpMediaImpls()
@@ -1725,18 +1739,25 @@ void LLVOVolume::cleanUpMediaImpls()
}
}
-void LLVOVolume::updateObjectMediaData(const LLSD &media_data_array)
+void LLVOVolume::updateObjectMediaData(const LLSD &media_data_array, const std::string &media_version)
{
// media_data_array is an array of media entry maps
+ // media_version is the version string in the response.
+ U32 fetched_version = LLTextureEntry::getVersionFromMediaVersionString(media_version);
- //llinfos << "updating:" << this->getID() << " " << ll_pretty_print_sd(media_data_array) << llendl;
-
- LLSD::array_const_iterator iter = media_data_array.beginArray();
- LLSD::array_const_iterator end = media_data_array.endArray();
- U8 texture_index = 0;
- for (; iter != end; ++iter, ++texture_index)
+ // Only update it if it is newer!
+ if ( (S32)fetched_version > mLastFetchedMediaVersion)
{
- syncMediaData(texture_index, *iter, false/*merge*/, false/*ignore_agent*/);
+ mLastFetchedMediaVersion = fetched_version;
+ //llinfos << "updating:" << this->getID() << " " << ll_pretty_print_sd(media_data_array) << llendl;
+
+ LLSD::array_const_iterator iter = media_data_array.beginArray();
+ LLSD::array_const_iterator end = media_data_array.endArray();
+ U8 texture_index = 0;
+ for (; iter != end; ++iter, ++texture_index)
+ {
+ syncMediaData(texture_index, *iter, false/*merge*/, false/*ignore_agent*/);
+ }
}
}
@@ -1904,7 +1925,7 @@ void LLVOVolume::mediaNavigated(LLViewerMediaImpl *impl, LLPluginClassMedia* plu
llinfos << "broadcasting navigate with URI " << new_location << llendl;
- sObjectMediaNavigateClient->navigate(new LLMediaDataClientObjectImpl(this), face_index, new_location);
+ sObjectMediaNavigateClient->navigate(new LLMediaDataClientObjectImpl(this, false), face_index, new_location);
}
}
@@ -1968,7 +1989,7 @@ void LLVOVolume::mediaEvent(LLViewerMediaImpl *impl, LLPluginClassMedia* plugin,
void LLVOVolume::sendMediaDataUpdate()
{
- sObjectMediaClient->updateMedia(new LLMediaDataClientObjectImpl(this));
+ sObjectMediaClient->updateMedia(new LLMediaDataClientObjectImpl(this, false));
}
void LLVOVolume::removeMediaImpl(S32 texture_index)
@@ -2063,7 +2084,7 @@ viewer_media_t LLVOVolume::getMediaImpl(U8 face_id) const
F64 LLVOVolume::getTotalMediaInterest() const
{
- F64 interest = (F64)0.0;
+ F64 interest = (F64)-1.0; // means not interested;
int i = 0;
const int end = getNumTEs();
for ( ; i < end; ++i)
@@ -2071,6 +2092,7 @@ F64 LLVOVolume::getTotalMediaInterest() const
const viewer_media_t &impl = getMediaImpl(i);
if (!impl.isNull())
{
+ if (interest == (F64)-1.0) interest = (F64)0.0;
interest += impl->getInterest();
}
}
diff --git a/indra/newview/llvovolume.h b/indra/newview/llvovolume.h
index 06e214b41e..0655c13d5a 100644
--- a/indra/newview/llvovolume.h
+++ b/indra/newview/llvovolume.h
@@ -238,7 +238,7 @@ public:
// Update this object's media data with the given media data array
// (typically this is only called upon a response from a server request)
- void updateObjectMediaData(const LLSD &media_data_array);
+ void updateObjectMediaData(const LLSD &media_data_array, const std::string &media_version);
// Bounce back media at the given index to its current URL (or home URL, if current URL is empty)
void mediaNavigateBounceBack(U8 texture_index);
@@ -270,13 +270,16 @@ public:
// Returns 'true' iff the media data for this object is in flight
bool isMediaDataBeingFetched() const;
+ // Returns the "last fetched" media version, or -1 if not fetched yet
+ S32 getLastFetchedMediaVersion() const { return mLastFetchedMediaVersion; }
+
protected:
S32 computeLODDetail(F32 distance, F32 radius);
BOOL calcLOD();
LLFace* addFace(S32 face_index);
void updateTEData();
- void requestMediaDataUpdate();
+ void requestMediaDataUpdate(bool isNew);
void cleanUpMediaImpls();
void addMediaImpl(LLViewerMediaImpl* media_impl, S32 texture_index) ;
void removeMediaImpl(S32 texture_index) ;
@@ -300,6 +303,7 @@ private:
LLPointer<LLViewerFetchedTexture> mSculptTexture;
LLPointer<LLViewerFetchedTexture> mLightTexture;
media_list_t mMediaImplList;
+ S32 mLastFetchedMediaVersion; // as fetched from the server, starts as -1
// statics
public:
diff --git a/indra/newview/tests/llmediadataclient_test.cpp b/indra/newview/tests/llmediadataclient_test.cpp
index 6ff2c9446e..a8e199e993 100644
--- a/indra/newview/tests/llmediadataclient_test.cpp
+++ b/indra/newview/tests/llmediadataclient_test.cpp
@@ -75,15 +75,13 @@
<string>baz</string> \
</array>"
-#define _DATA_URLS(ID,DIST,INT,URL1,URL2) " \
+#define _DATA_URLS(ID,INTEREST,NEW,URL1,URL2) " \
<llsd> \
<map> \
<key>uuid</key> \
<string>" ID "</string> \
- <key>distance</key> \
- <real>" DIST "</real> \
<key>interest</key> \
- <real>" INT "</real> \
+ <real>" INTEREST "</real> \
<key>cap_urls</key> \
<map> \
<key>ObjectMedia</key> \
@@ -93,18 +91,22 @@
</map> \
<key>media_data</key> \
" MEDIA_DATA " \
+ <key>is_dead</key> \
+ <boolean>false</boolean> \
+ <key>is_new</key> \
+ <boolean>" NEW "</boolean> \
</map> \
</llsd>"
-#define _DATA(ID,DIST,INT) _DATA_URLS(ID,DIST,INT,FAKE_OBJECT_MEDIA_CAP_URL,FAKE_OBJECT_MEDIA_NAVIGATE_CAP_URL)
+#define _DATA(ID,INTEREST,NEW) _DATA_URLS(ID,INTEREST,NEW,FAKE_OBJECT_MEDIA_CAP_URL,FAKE_OBJECT_MEDIA_NAVIGATE_CAP_URL)
-const char *DATA = _DATA(VALID_OBJECT_ID,"1.0","1.0");
+const char *DATA = _DATA(VALID_OBJECT_ID,"1.0","true");
#define STR(I) boost::lexical_cast<std::string>(I)
#define LOG_TEST(N) LL_DEBUGS("LLMediaDataClient") << "\n" << \
"================================================================================\n" << \
-"===================================== TEST " #N " ===================================\n" << \
+"==================================== TEST " #N " ===================================\n" << \
"================================================================================\n" << LL_ENDL;
LLSD *gPostRecords = NULL;
@@ -125,21 +127,20 @@ void LLHTTPClient::post(
gPostRecords->append(record);
// Magic URL that triggers a 503:
+ LLSD result;
+ result[LLTextureEntry::OBJECT_ID_KEY] = body[LLTextureEntry::OBJECT_ID_KEY];
if ( url == FAKE_OBJECT_MEDIA_CAP_URL_503 )
{
responder->error(HTTP_SERVICE_UNAVAILABLE, "fake reason");
+ return;
}
else if (url == FAKE_OBJECT_MEDIA_NAVIGATE_CAP_URL_ERROR)
{
- LLSD result;
LLSD error;
error["code"] = LLObjectMediaNavigateClient::ERROR_PERMISSION_DENIED_CODE;
result["error"] = error;
- responder->result(result);
- }
- else {
- responder->result(LLSD());
- }
+ }
+ responder->result(result);
}
const F32 HTTP_REQUEST_EXPIRY_SECS = 60.0f;
@@ -152,13 +153,12 @@ public:
std::istringstream d(data);
LLSDSerialize::fromXML(mRep, d);
mNumBounceBacks = 0;
- mDead = false;
// std::cout << ll_pretty_print_sd(mRep) << std::endl;
// std::cout << "ID: " << getID() << std::endl;
}
LLMediaDataClientObjectTest(const LLSD &rep)
- : mRep(rep), mNumBounceBacks(0), mDead(false) {}
+ : mRep(rep), mNumBounceBacks(0) {}
~LLMediaDataClientObjectTest()
{ LL_DEBUGS("LLMediaDataClient") << "~LLMediaDataClientObjectTest" << LL_ENDL; }
@@ -169,43 +169,44 @@ public:
virtual LLUUID getID() const
{ return mRep["uuid"]; }
virtual void mediaNavigateBounceBack(U8 index)
- {
- mNumBounceBacks++;
- }
+ { mNumBounceBacks++; }
virtual bool hasMedia() const
{ return mRep.has("media_data"); }
- virtual void updateObjectMediaData(LLSD const &media_data_array)
- { mRep["media_data"] = media_data_array; }
-
- virtual F64 getDistanceFromAvatar() const
- { return (LLSD::Real)mRep["distance"]; }
-
- virtual F64 getTotalMediaInterest() const
+ virtual void updateObjectMediaData(LLSD const &media_data_array, const std::string &media_version)
+ { mRep["media_data"] = media_data_array; mRep["media_version"] = media_version; }
+
+ virtual F64 getMediaInterest() const
{ return (LLSD::Real)mRep["interest"]; }
virtual std::string getCapabilityUrl(const std::string &name) const
{ return mRep["cap_urls"][name]; }
virtual bool isDead() const
- { return mDead; }
-
- void setDistanceFromAvatar(F64 val)
- { mRep["distance"] = val; }
+ { return mRep["is_dead"]; }
+
+ virtual U32 getMediaVersion() const
+ { return (LLSD::Integer)mRep["media_version"]; }
- void setTotalMediaInterest(F64 val)
+ virtual bool isNew() const
+ { return mRep["is_new"]; }
+
+ void setMediaInterest(F64 val)
{ mRep["interest"] = val; }
int getNumBounceBacks() const
{ return mNumBounceBacks; }
void markDead()
- { mDead = true; }
+ { mRep["is_dead"] = true; }
+
+ void markOld()
+ { mRep["is_new"] = false; }
+
private:
LLSD mRep;
int mNumBounceBacks;
- bool mDead;
};
// This special timer delay should ensure that the timer will fire on the very
@@ -225,9 +226,9 @@ namespace tut
mediadataclient() {
gPostRecords = &mLLSD;
- //LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
- //LLError::setClassLevel("LLMediaDataClient", LLError::LEVEL_DEBUG);
- //LLError::setTagLevel("MediaOnAPrim", LLError::LEVEL_DEBUG);
+ LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
+ LLError::setClassLevel("LLMediaDataClient", LLError::LEVEL_DEBUG);
+ LLError::setTagLevel("MediaOnAPrim", LLError::LEVEL_DEBUG);
}
LLSD mLLSD;
};
@@ -378,11 +379,11 @@ namespace tut
LOG_TEST(4);
LLMediaDataClientObject::ptr_t o1 = new LLMediaDataClientObjectTest(
- _DATA(VALID_OBJECT_ID_1,"3.0","1.0"));
+ _DATA(VALID_OBJECT_ID_1,"1.0","true"));
LLMediaDataClientObject::ptr_t o2 = new LLMediaDataClientObjectTest(
- _DATA(VALID_OBJECT_ID_2,"1.0","1.0"));
+ _DATA(VALID_OBJECT_ID_2,"3.0","true"));
LLMediaDataClientObject::ptr_t o3 = new LLMediaDataClientObjectTest(
- _DATA(VALID_OBJECT_ID_3,"2.0","1.0"));
+ _DATA(VALID_OBJECT_ID_3,"2.0","true"));
{
LLPointer<LLObjectMediaDataClient> mdc = new LLObjectMediaDataClient(NO_PERIOD,NO_PERIOD);
const char *ORDERED_OBJECT_IDS[] = { VALID_OBJECT_ID_2, VALID_OBJECT_ID_3, VALID_OBJECT_ID_1 };
@@ -428,8 +429,7 @@ namespace tut
LLMediaDataClientObject::ptr_t o = new LLMediaDataClientObjectTest(
_DATA_URLS(VALID_OBJECT_ID,
- "1.0",
- "1.0",
+ "1.0","true",
FAKE_OBJECT_MEDIA_CAP_URL_503,
FAKE_OBJECT_MEDIA_NAVIGATE_CAP_URL));
int num_refs_start = o->getNumRefs();
@@ -484,8 +484,7 @@ namespace tut
LLMediaDataClientObject::ptr_t o = new LLMediaDataClientObjectTest(
_DATA_URLS(VALID_OBJECT_ID,
- "1.0",
- "1.0",
+ "1.0","true",
FAKE_OBJECT_MEDIA_CAP_URL,
FAKE_OBJECT_MEDIA_NAVIGATE_CAP_URL_ERROR));
{
@@ -517,9 +516,9 @@ namespace tut
LOG_TEST(7);
LLMediaDataClientObject::ptr_t o1 = new LLMediaDataClientObjectTest(
- _DATA(VALID_OBJECT_ID_1,"3.0","1.0"));
+ _DATA(VALID_OBJECT_ID_1,"3.0","true"));
LLMediaDataClientObject::ptr_t o2 = new LLMediaDataClientObjectTest(
- _DATA(VALID_OBJECT_ID_2,"1.0","1.0"));
+ _DATA(VALID_OBJECT_ID_2,"1.0","true"));
int num_refs_start = o1->getNumRefs();
{
LLPointer<LLObjectMediaDataClient> mdc = new LLObjectMediaDataClient(NO_PERIOD,NO_PERIOD);
@@ -551,10 +550,10 @@ namespace tut
// Test queue handling of objects that are marked dead.
LOG_TEST(8);
- LLMediaDataClientObject::ptr_t o1 = new LLMediaDataClientObjectTest(_DATA(VALID_OBJECT_ID_1,"1.0","1.0"));
- LLMediaDataClientObject::ptr_t o2 = new LLMediaDataClientObjectTest(_DATA(VALID_OBJECT_ID_2,"2.0","1.0"));
- LLMediaDataClientObject::ptr_t o3 = new LLMediaDataClientObjectTest(_DATA(VALID_OBJECT_ID_3,"3.0","1.0"));
- LLMediaDataClientObject::ptr_t o4 = new LLMediaDataClientObjectTest(_DATA(VALID_OBJECT_ID_4,"4.0","1.0"));
+ LLMediaDataClientObject::ptr_t o1 = new LLMediaDataClientObjectTest(_DATA(VALID_OBJECT_ID_1,"4.0","true"));
+ LLMediaDataClientObject::ptr_t o2 = new LLMediaDataClientObjectTest(_DATA(VALID_OBJECT_ID_2,"3.0","true"));
+ LLMediaDataClientObject::ptr_t o3 = new LLMediaDataClientObjectTest(_DATA(VALID_OBJECT_ID_3,"2.0","true"));
+ LLMediaDataClientObject::ptr_t o4 = new LLMediaDataClientObjectTest(_DATA(VALID_OBJECT_ID_4,"1.0","true"));
{
LLPointer<LLObjectMediaDataClient> mdc = new LLObjectMediaDataClient(NO_PERIOD,NO_PERIOD);
@@ -616,10 +615,11 @@ namespace tut
//
LOG_TEST(9);
- LLMediaDataClientObject::ptr_t o1 = new LLMediaDataClientObjectTest(_DATA(VALID_OBJECT_ID_1,"10.0","1.0"));
- LLMediaDataClientObject::ptr_t o2 = new LLMediaDataClientObjectTest(_DATA(VALID_OBJECT_ID_2,"20.0","1.0"));
- LLMediaDataClientObject::ptr_t o3 = new LLMediaDataClientObjectTest(_DATA(VALID_OBJECT_ID_3,"30.0","1.0"));
- LLMediaDataClientObject::ptr_t o4 = new LLMediaDataClientObjectTest(_DATA(VALID_OBJECT_ID_4,"40.0","1.0"));
+ LLMediaDataClientObject::ptr_t o1 = new LLMediaDataClientObjectTest(_DATA(VALID_OBJECT_ID_1,"40.0","true"));
+ LLMediaDataClientObject::ptr_t o2 = new LLMediaDataClientObjectTest(_DATA(VALID_OBJECT_ID_2,"30.0","true"));
+ LLMediaDataClientObject::ptr_t o3 = new LLMediaDataClientObjectTest(_DATA(VALID_OBJECT_ID_3,"20.0","true"));
+ LLMediaDataClientObjectTest *object4 = new LLMediaDataClientObjectTest(_DATA(VALID_OBJECT_ID_4,"10.0","true"));
+ LLMediaDataClientObject::ptr_t o4 = object4;
{
LLPointer<LLObjectMediaDataClient> mdc = new LLObjectMediaDataClient(NO_PERIOD,NO_PERIOD);
@@ -650,8 +650,7 @@ namespace tut
// Now, pretend that object 4 moved relative to the avatar such
// that it is now closest
- static_cast<LLMediaDataClientObjectTest*>(
- static_cast<LLMediaDataClientObject*>(o4))->setDistanceFromAvatar(5.0);
+ object4->setMediaInterest(50.0);
::pump_timers();
++test_num;
@@ -686,4 +685,105 @@ namespace tut
ensure("refcount of o4", o4->getNumRefs(), 1);
}
+
+ template<> template<>
+ void mediadataclient_object_t::test<10>()
+ {
+ //
+ // Test using the "round-robin" queue
+ //
+ LOG_TEST(10);
+
+ LLMediaDataClientObject::ptr_t o1 = new LLMediaDataClientObjectTest(_DATA(VALID_OBJECT_ID_1,"1.0","true"));
+ LLMediaDataClientObject::ptr_t o2 = new LLMediaDataClientObjectTest(_DATA(VALID_OBJECT_ID_2,"2.0","true"));
+ LLMediaDataClientObject::ptr_t o3 = new LLMediaDataClientObjectTest(_DATA(VALID_OBJECT_ID_3,"3.0","false"));
+ LLMediaDataClientObject::ptr_t o4 = new LLMediaDataClientObjectTest(_DATA(VALID_OBJECT_ID_4,"4.0","false"));
+ {
+ LLPointer<LLObjectMediaDataClient> mdc = new LLObjectMediaDataClient(NO_PERIOD,NO_PERIOD);
+
+ // queue up all 4 objects. The first two should be in the sorted
+ // queue [2 1], the second in the round-robin queue. The queues
+ // are serviced interleaved, so we should expect:
+ // 2, 4, 1, 3
+ mdc->fetchMedia(o1);
+ mdc->fetchMedia(o2);
+ mdc->fetchMedia(o3);
+ mdc->fetchMedia(o4);
+
+ int test_num = 0;
+
+ // 0
+ ensure(STR(test_num) + ". is in queue 1", mdc->isInQueue(o1));
+ ensure(STR(test_num) + ". is in queue 2", mdc->isInQueue(o2));
+ ensure(STR(test_num) + ". is in queue 3", mdc->isInQueue(o3));
+ ensure(STR(test_num) + ". is in queue 4", mdc->isInQueue(o4));
+ ensure(STR(test_num) + ". post records", gPostRecords->size(), 0);
+
+ ::pump_timers();
+ ++test_num;
+
+ // 1 The first tick should remove object 2
+ ensure(STR(test_num) + ". is not in queue 2", !mdc->isInQueue(o2));
+ ensure(STR(test_num) + ". is in queue 1", mdc->isInQueue(o1));
+ ensure(STR(test_num) + ". is in queue 3", mdc->isInQueue(o3));
+ ensure(STR(test_num) + ". is in queue 4", mdc->isInQueue(o4));
+ ensure(STR(test_num) + ". post records", gPostRecords->size(), 1);
+ ensure(STR(test_num) + ". post object id", (*gPostRecords)[0]["body"][LLTextureEntry::OBJECT_ID_KEY].asUUID(), LLUUID(VALID_OBJECT_ID_2));
+
+ ::pump_timers();
+ ++test_num;
+
+ // 2 The second tick should send object 4, but it will still be
+ // "in the queue"
+ ensure(STR(test_num) + ". is not in queue 2", !mdc->isInQueue(o2));
+ ensure(STR(test_num) + ". is in queue 1", mdc->isInQueue(o1));
+ ensure(STR(test_num) + ". is in queue 3", mdc->isInQueue(o3));
+ ensure(STR(test_num) + ". is in queue 4", mdc->isInQueue(o4));
+ ensure(STR(test_num) + ". post records", gPostRecords->size(), 2);
+ ensure(STR(test_num) + ". post object id", (*gPostRecords)[1]["body"][LLTextureEntry::OBJECT_ID_KEY].asUUID(), LLUUID(VALID_OBJECT_ID_4));
+
+ ::pump_timers();
+ ++test_num;
+
+ // 3 The third tick should remove object 1
+ ensure(STR(test_num) + ". is not in queue 2", !mdc->isInQueue(o2));
+ ensure(STR(test_num) + ". is not in queue 1", !mdc->isInQueue(o1));
+ ensure(STR(test_num) + ". is in queue 3", mdc->isInQueue(o3));
+ ensure(STR(test_num) + ". is in queue 4", mdc->isInQueue(o4));
+ ensure(STR(test_num) + ". post records", gPostRecords->size(), 3);
+ ensure(STR(test_num) + ". post object id", (*gPostRecords)[2]["body"][LLTextureEntry::OBJECT_ID_KEY].asUUID(), LLUUID(VALID_OBJECT_ID_1));
+
+ ::pump_timers();
+ ++test_num;
+
+ // 4 The fourth tick should send object 3, but it will still be
+ // "in the queue"
+ ensure(STR(test_num) + ". is not in queue 2", !mdc->isInQueue(o2));
+ ensure(STR(test_num) + ". is not in queue 1", !mdc->isInQueue(o1));
+ ensure(STR(test_num) + ". is in queue 3", mdc->isInQueue(o3));
+ ensure(STR(test_num) + ". is in queue 4", mdc->isInQueue(o4));
+ ensure(STR(test_num) + ". post records", gPostRecords->size(), 4);
+ ensure(STR(test_num) + ". post object id", (*gPostRecords)[3]["body"][LLTextureEntry::OBJECT_ID_KEY].asUUID(), LLUUID(VALID_OBJECT_ID_3));
+
+ ::pump_timers();
+ ++test_num;
+
+ // 5 The fifth tick should now identify objects 3 and 4 as no longer
+ // needing "updating", and remove them from the queue
+ ensure(STR(test_num) + ". is not in queue 2", !mdc->isInQueue(o2));
+ ensure(STR(test_num) + ". is not in queue 1", !mdc->isInQueue(o1));
+ ensure(STR(test_num) + ". is not in queue 3", !mdc->isInQueue(o3));
+ ensure(STR(test_num) + ". is not in queue 4", !mdc->isInQueue(o4));
+ ensure(STR(test_num) + ". post records", gPostRecords->size(), 4);
+
+ ::pump_timers();
+
+ // Whew....better be empty
+ ensure("queue empty", mdc->isEmpty());
+ }
+ ensure("refcount of o1", o1->getNumRefs(), 1);
+ ensure("refcount of o2", o2->getNumRefs(), 1);
+ ensure("refcount of o3", o3->getNumRefs(), 1);
+ ensure("refcount of o4", o4->getNumRefs(), 1);
+ }
}