diff options
-rwxr-xr-x | indra/newview/llmediadataclient.cpp | 817 | ||||
-rwxr-xr-x | indra/newview/llmediadataclient.h | 168 | ||||
-rw-r--r-- | indra/newview/llvovolume.cpp | 32 | ||||
-rw-r--r-- | indra/newview/llvovolume.h | 5 | ||||
-rw-r--r-- | indra/newview/tests/llmediadataclient_test.cpp | 95 |
5 files changed, 725 insertions, 392 deletions
diff --git a/indra/newview/llmediadataclient.cpp b/indra/newview/llmediadataclient.cpp index f0091d6fdc..a577776ad0 100755 --- a/indra/newview/llmediadataclient.cpp +++ b/indra/newview/llmediadataclient.cpp @@ -97,6 +97,54 @@ const U32 LLMediaDataClient::MAX_ROUND_ROBIN_QUEUE_SIZE = 10000; std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::request_queue_t &q); std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::Request &q); +template <typename T> +static typename T::iterator find_matching_request(T &c, const LLMediaDataClient::Request *request, LLMediaDataClient::Request::Type match_type = LLMediaDataClient::Request::ANY) +{ + for(typename T::iterator iter = c.begin(); iter != c.end(); ++iter) + { + if(request->isMatch(*iter, match_type)) + { + return iter; + } + } + + return c.end(); +} + +template <typename T> +static typename T::iterator find_matching_request(T &c, const LLUUID &id, LLMediaDataClient::Request::Type match_type = LLMediaDataClient::Request::ANY) +{ + for(typename T::iterator iter = c.begin(); iter != c.end(); ++iter) + { + if(((*iter)->getID() == id) && ((match_type == LLMediaDataClient::Request::ANY) || (match_type == (*iter)->getType()))) + { + return iter; + } + } + + return c.end(); +} + +// NOTE: remove_matching_requests will not work correctly for containers where deleting an element may invalidate iterators +// to other elements in the container (such as std::vector). +// If the implementation is changed to use a container with this property, this will need to be revisited. +template <typename T> +static void remove_matching_requests(T &c, const LLUUID &id, LLMediaDataClient::Request::Type match_type = LLMediaDataClient::Request::ANY) +{ + for(typename T::iterator iter = c.begin(); iter != c.end();) + { + typename T::value_type i = *iter; + typename T::iterator next = iter; + next++; + if((i->getID() == id) && ((match_type == LLMediaDataClient::Request::ANY) || (match_type == i->getType()))) + { + i->markDead(); + c.erase(iter); + } + iter = next; + } +} + ////////////////////////////////////////////////////////////////////////////////////// // // LLMediaDataClient @@ -113,107 +161,36 @@ LLMediaDataClient::LLMediaDataClient(F32 queue_timer_delay, mMaxNumRetries(max_retries), mMaxSortedQueueSize(max_sorted_queue_size), mMaxRoundRobinQueueSize(max_round_robin_queue_size), - mQueueTimerIsRunning(false), - mCurrentQueueIsTheSortedQueue(true) + mQueueTimerIsRunning(false) { } LLMediaDataClient::~LLMediaDataClient() { stopQueueTimer(); - - // 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(); } bool LLMediaDataClient::isEmpty() const { - return mSortedQueue.empty() && mRoundRobinQueue.empty(); + return mQueue.empty(); } bool LLMediaDataClient::isInQueue(const LLMediaDataClientObject::ptr_t &object) { - return (LLMediaDataClient::findOrRemove(mSortedQueue, object, false/*remove*/, LLMediaDataClient::Request::ANY).notNull() - || (LLMediaDataClient::findOrRemove(mRoundRobinQueue, object, false/*remove*/, LLMediaDataClient::Request::ANY).notNull())); -} - -bool LLMediaDataClient::removeFromQueue(const LLMediaDataClientObject::ptr_t &object) -{ - 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; + if(find_matching_request(mQueue, object->getID()) != mQueue.end()) + return true; + + if(find_matching_request(mUnQueuedRequests, object->getID()) != mUnQueuedRequests.end()) + return true; + + return false; } -//static -LLMediaDataClient::request_ptr_t LLMediaDataClient::findOrRemove(request_queue_t &queue, const LLMediaDataClientObject::ptr_t &obj, bool remove, LLMediaDataClient::Request::Type type) +void LLMediaDataClient::removeFromQueue(const LLMediaDataClientObject::ptr_t &object) { - 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; -} - -void LLMediaDataClient::enqueue(Request *request) -{ - 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; - - mSortedQueue.push_back(request); - - LL_DEBUGS("LLMediaDataClientQueue") << "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 - mRoundRobinQueue.push_front(request); - - LL_DEBUGS("LLMediaDataClientQueue") << "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(); + LL_DEBUGS("LLMediaDataClient") << "removing requests matching ID " << object->getID() << LL_ENDL; + remove_matching_requests(mQueue, object->getID()); + remove_matching_requests(mUnQueuedRequests, object->getID()); } void LLMediaDataClient::startQueueTimer() @@ -225,7 +202,7 @@ void LLMediaDataClient::startQueueTimer() new QueueTimer(mQueueTimerDelay, this); } else { - LL_DEBUGS("LLMediaDataClient") << "not starting queue timer (it's already running, right???)" << LL_ENDL; + LL_DEBUGS("LLMediaDataClient") << "queue timer is already running" << LL_ENDL; } } @@ -236,227 +213,138 @@ void LLMediaDataClient::stopQueueTimer() bool LLMediaDataClient::processQueueTimer() { - sortQueue(); - - if(!isEmpty()) - { - LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() started, SORTED queue size is: " << mSortedQueue.size() - << ", RR queue size is: " << mRoundRobinQueue.size() << LL_ENDL; - LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() started, SORTED queue is: " << mSortedQueue << LL_ENDL; - LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() started, RR queue is: " << mRoundRobinQueue << LL_ENDL; - } - + if(isEmpty()) + return true; + + LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() started, queue size is: " << mQueue.size() << LL_ENDL; + LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() started, SORTED queue is: " << mQueue << 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("LLMediaDataClientQueue") << "QueueTimer::tick() finished, SORTED queue is: " << mSortedQueue << LL_ENDL; - LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() finished, RR queue is: " << mRoundRobinQueue << LL_ENDL; + LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() finished, queue size is: " << mQueue.size() << LL_ENDL; + LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() finished, SORTED queue is: " << mQueue << LL_ENDL; return isEmpty(); } -bool LLMediaDataClient::request_needs_purge(LLMediaDataClient::request_ptr_t request) +LLMediaDataClient::request_ptr_t LLMediaDataClient::dequeue() { - // Check for conditions that should cause a request to get removed from the queue - if(request.isNull()) - { - LL_WARNS("LLMediaDataClient") << "removing NULL request" << LL_ENDL; - return true; - } - - // For some reason, the existing code put sent items onto the back of the round-robin queue and let them come through again - // before stripping them off the front. Was there a good reason for this? -// if(request->isMarkedSent()) -// { -// LL_INFOS("LLMediaDataClient") << "removing : " << *request << " request is marked sent" << LL_ENDL; -// return true; -// } - - const LLMediaDataClientObject *object = request->getObject(); + request_ptr_t request; + request_queue_t *queue_p = getQueue(); - if(object == NULL) + if (queue_p->empty()) { - LL_INFOS("LLMediaDataClient") << "removing : " << *request << " object is NULL" << LL_ENDL; - return true; + LL_DEBUGS("LLMediaDataClient") << "queue empty: " << (*queue_p) << LL_ENDL; } - - if(object->isDead()) - { - LL_INFOS("LLMediaDataClient") << "removing : " << *request << " object is dead" << LL_ENDL; - return true; - } - - if(!object->hasMedia()) + else { - LL_INFOS("LLMediaDataClient") << "removing : " << *request << " object has no media" << LL_ENDL; - return true; + request = queue_p->front(); + + if(canServiceRequest(request)) + { + // We will be returning this request, so remove it from the queue. + queue_p->pop_front(); + } + else + { + // Don't return this request -- it's not ready to be serviced. + request = NULL; + } } - return false; + return request; +} + +void LLMediaDataClient::pushBack(request_ptr_t request) +{ + request_queue_t *queue_p = getQueue(); + queue_p->push_front(request); } -void LLMediaDataClient::sortQueue() +void LLMediaDataClient::trackRequest(request_ptr_t request) { - if(!mSortedQueue.empty()) + request_set_t::iterator iter = mUnQueuedRequests.lower_bound(request); + + if(*iter == request) { - // Cull dead objects and score all remaining items first - request_queue_t::iterator iter = mSortedQueue.begin(); - request_queue_t::iterator end = mSortedQueue.end(); - while (iter != end) - { - if(request_needs_purge(*iter)) - { - iter = mSortedQueue.erase(iter); - } - else - { - (*iter)->updateScore(); - iter++; - } - } - - // 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_ONCE("LLMediaDataClient") << "sorted queue MAXED OUT! Culling " - << num_to_cull << " items" << LL_ENDL; - while (num_to_cull-- > 0) - { - mSortedQueue.pop_back(); - } - } + LL_WARNS("LLMediaDataClient") << "Tracking already tracked request: " << *request << LL_ENDL; } - - // Cull dead items from the round robin queue as well. - for(request_queue_t::iterator iter = mRoundRobinQueue.begin(); iter != mRoundRobinQueue.end();) + else { - if(request_needs_purge(*iter)) - { - iter = mRoundRobinQueue.erase(iter); - } - else - { - iter++; - } + mUnQueuedRequests.insert(iter, request); } } -// static -bool LLMediaDataClient::compareRequests(const request_ptr_t &o1, const request_ptr_t &o2) +void LLMediaDataClient::stopTrackingRequest(request_ptr_t request) { - if (o2.isNull()) return true; - if (o1.isNull()) return false; - return ( o1->getScore() > o2->getScore() ); + request_set_t::iterator iter = mUnQueuedRequests.find(request); + + if(*iter == request) + { + mUnQueuedRequests.erase(iter); + } + else + { + LL_WARNS("LLMediaDataClient") << "Removing an untracked request: " << *request << LL_ENDL; + } } void LLMediaDataClient::serviceQueue() { - request_queue_t *queue_p = getCurrentQueue(); + // Peel one off of the items from the queue and execute it + request_ptr_t request; - // quick retry loop for cases where we shouldn't wait for the next timer tick - while(true) + do { - if (queue_p->empty()) + request = dequeue(); + + if(request.isNull()) { - LL_DEBUGS("LLMediaDataClient") << "queue empty: " << (*queue_p) << LL_ENDL; - break; + // Queue is empty. + return; } - - // Peel one off of the items from the queue, and execute request - request_ptr_t request = queue_p->front(); - llassert(!request.isNull()); - const LLMediaDataClientObject *object = (request.isNull()) ? NULL : request->getObject(); - llassert(NULL != object); - - // Check for conditions that would make us just pop and rapidly loop through - // the queue. - if(request->isMarkedSent()) + + if(request->isDead()) { - // Sent items that make it back to the head of the queue need to be skipped. - LL_INFOS("LLMediaDataClient") << "removing : " << *request << " request is marked sent" << LL_ENDL; - - queue_p->pop_front(); - continue; // jump back to the start of the quick retry loop + LL_INFOS("LLMediaDataClient") << "Skipping dead request " << *request << LL_ENDL; + continue; } + + } while(false); - // Next, ask if this is "interesting enough" to fetch. If not, just stop - // and wait for the next timer go-round. Only do this for the sorted - // queue. - if (mCurrentQueueIsTheSortedQueue && !object->isInterestingEnough()) - { - LL_DEBUGS("LLMediaDataClient") << "Not fetching " << *request << ": not interesting enough" << LL_ENDL; - break; - } + // try to send the HTTP message to the cap url + std::string url = request->getCapability(); + if (!url.empty()) + { + const LLSD &sd_payload = request->getPayload(); + LL_INFOS("LLMediaDataClient") << "Sending request for " << *request << LL_ENDL; - // Finally, try to send the HTTP message to the cap url - std::string url = request->getCapability(); - bool maybe_retry = false; - if (!url.empty()) - { - 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, request->createResponder()); - } - else { - LL_INFOS("LLMediaDataClient") << "NOT Sending request for " << *request << ": empty cap url!" << LL_ENDL; - maybe_retry = true; - } - - bool exceeded_retries = request->getRetryCount() > mMaxNumRetries; - if (maybe_retry && ! exceeded_retries) // Try N times before giving up + // Add this request to the non-queued tracking list + trackRequest(request); + + // and make the post + LLHTTPClient::post(url, sd_payload, request->createResponder()); + } + else + { + // Cap url doesn't exist. + + if(request->getRetryCount() < mMaxNumRetries) { - // We got an empty cap, but in that case we will retry again next - // timer fire. + LL_WARNS("LLMediaDataClient") << "Could not send request " << *request << " (empty cap url), will retry." << LL_ENDL; + // Put this request back at the head of its queue, and retry next time the queue timer fires. request->incRetryCount(); + pushBack(request); } - else { - if (exceeded_retries) - { - LL_WARNS("LLMediaDataClient") << "Could not send request " << *request << " for " - << mMaxNumRetries << " tries...popping object id " << object->getID() << LL_ENDL; - // XXX Should we bring up a warning dialog?? - } - - queue_p->pop_front(); - - if (! mCurrentQueueIsTheSortedQueue) { - // Round robin - request->markSent(true); - mRoundRobinQueue.push_back(request); - } + else + { + // This request has exceeded its maxumim retry count. It will be dropped. + LL_WARNS("LLMediaDataClient") << "Could not send request " << *request << " for " << mMaxNumRetries << " tries, dropping request." << LL_ENDL; } - - // end of quick loop -- any cases where we want to loop will use 'continue' to jump back to the start. - break; - } - - swapCurrentQueue(); -} -void LLMediaDataClient::swapCurrentQueue() -{ - // Swap - mCurrentQueueIsTheSortedQueue = !mCurrentQueueIsTheSortedQueue; - // If its empty, swap back - if (getCurrentQueue()->empty()) - { - mCurrentQueueIsTheSortedQueue = !mCurrentQueueIsTheSortedQueue; } } -LLMediaDataClient::request_queue_t *LLMediaDataClient::getCurrentQueue() -{ - return (mCurrentQueueIsTheSortedQueue) ? &mSortedQueue : &mRoundRobinQueue; -} // dump the queue std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::request_queue_t &q) @@ -466,7 +354,7 @@ std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::request_queue LLMediaDataClient::request_queue_t::const_iterator end = q.end(); while (iter != end) { - s << "\t" << i << "]: " << (*iter)->getObject()->getID().asString() << "(" << (*iter)->getObject()->getMediaInterest() << ")"; + s << "\t" << i << "]: " << (*iter)->getID().asString() << "(" << (*iter)->getObject()->getMediaInterest() << ")"; iter++; i++; } @@ -513,19 +401,29 @@ BOOL LLMediaDataClient::QueueTimer::tick() // ////////////////////////////////////////////////////////////////////////////////////// -LLMediaDataClient::Responder::RetryTimer::RetryTimer(F32 time, Responder *mdr) -: LLEventTimer(time), mResponder(mdr) +LLMediaDataClient::RetryTimer::RetryTimer(F32 time, request_ptr_t request) +: LLEventTimer(time), mRequest(request) { + mRequest->startTracking(); } // virtual -BOOL LLMediaDataClient::Responder::RetryTimer::tick() +BOOL LLMediaDataClient::RetryTimer::tick() { - LL_INFOS("LLMediaDataClient") << "RetryTimer fired for: " << *(mResponder->getRequest()) << " retrying" << LL_ENDL; - mResponder->getRequest()->reEnqueue(); + mRequest->stopTracking(); + + if(mRequest->isDead()) + { + LL_INFOS("LLMediaDataClient") << "RetryTimer fired for dead request: " << *mRequest << ", aborting." << LL_ENDL; + } + else + { + LL_INFOS("LLMediaDataClient") << "RetryTimer fired for: " << *mRequest << ", retrying." << LL_ENDL; + mRequest->reEnqueue(); + } - // Release the ref to the responder. - mResponder = NULL; + // Release the ref to the request. + mRequest = NULL; // Don't fire again return TRUE; @@ -541,25 +439,35 @@ BOOL LLMediaDataClient::Responder::RetryTimer::tick() LLMediaDataClient::Request::Request(Type in_type, LLMediaDataClientObject *obj, - LLMediaDataClient *mdc) + LLMediaDataClient *mdc, + S32 face) : mType(in_type), mObject(obj), mNum(++sNum), mRetryCount(0), mMDC(mdc), - mMarkedSent(false), - mScore((F64)0.0) + mScore((F64)0.0), + mFace(face) { + mObjectID = mObject->getID(); } const char *LLMediaDataClient::Request::getCapName() const { - return mMDC->getCapabilityName(); + if(mMDC) + return mMDC->getCapabilityName(); + + return ""; } std::string LLMediaDataClient::Request::getCapability() const { - return getObject()->getCapabilityUrl(getCapName()); + if(mMDC) + { + return getObject()->getCapabilityUrl(getCapName()); + } + + return ""; } const char *LLMediaDataClient::Request::getTypeAsString() const @@ -586,31 +494,28 @@ const char *LLMediaDataClient::Request::getTypeAsString() const void LLMediaDataClient::Request::reEnqueue() { - mMDC->enqueue(this); + if(mMDC) + { + mMDC->enqueue(this); + } } F32 LLMediaDataClient::Request::getRetryTimerDelay() const { - return mMDC->mRetryTimerDelay; + if(mMDC) + return mMDC->mRetryTimerDelay; + + return 0.0f; } U32 LLMediaDataClient::Request::getMaxNumRetries() const { - return mMDC->mMaxNumRetries; + if(mMDC) + return mMDC->mMaxNumRetries; + + return 0; } -void LLMediaDataClient::Request::markSent(bool flag) -{ - if (mMarkedSent != flag) - { - mMarkedSent = flag; - if (!mMarkedSent) - { - mNum = ++sNum; - } - } -} - void LLMediaDataClient::Request::updateScore() { F64 tmp = mObject->getMediaInterest(); @@ -621,15 +526,37 @@ void LLMediaDataClient::Request::updateScore() } } +void LLMediaDataClient::Request::markDead() +{ + mMDC = NULL; +} + +bool LLMediaDataClient::Request::isDead() +{ + return ((mMDC == NULL) || mObject->isDead()); +} + +void LLMediaDataClient::Request::startTracking() +{ + if(mMDC) + mMDC->trackRequest(this); +} + +void LLMediaDataClient::Request::stopTracking() +{ + if(mMDC) + mMDC->stopTrackingRequest(this); +} + std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::Request &r) { s << "request: num=" << r.getNum() << " type=" << r.getTypeAsString() - << " ID=" << r.getObject()->getID() + << " ID=" << r.getID() + << " face=" << r.getFace() << " #retries=" << r.getRetryCount(); return s; } - ////////////////////////////////////////////////////////////////////////////////////// // @@ -645,6 +572,14 @@ LLMediaDataClient::Responder::Responder(const request_ptr_t &request) /*virtual*/ void LLMediaDataClient::Responder::error(U32 status, const std::string& reason) { + mRequest->stopTracking(); + + if(mRequest->isDead()) + { + LL_WARNS("LLMediaDataClient") << "dead request " << *mRequest << LL_ENDL; + return; + } + if (status == HTTP_SERVICE_UNAVAILABLE) { F32 retry_timeout = mRequest->getRetryTimerDelay(); @@ -657,14 +592,16 @@ void LLMediaDataClient::Responder::error(U32 status, const std::string& reason) // Start timer (instances are automagically tracked by // InstanceTracker<> and LLEventTimer) - new RetryTimer(F32(retry_timeout/*secs*/), this); + new RetryTimer(F32(retry_timeout/*secs*/), mRequest); } - else { + else + { LL_INFOS("LLMediaDataClient") << *mRequest << " got SERVICE_UNAVAILABLE...retry count " << mRequest->getRetryCount() << " exceeds " << mRequest->getMaxNumRetries() << ", not retrying" << LL_ENDL; } } - else { + else + { std::string msg = boost::lexical_cast<std::string>(status) + ": " + reason; LL_WARNS("LLMediaDataClient") << *mRequest << " http error(" << msg << ")" << LL_ENDL; } @@ -673,6 +610,14 @@ void LLMediaDataClient::Responder::error(U32 status, const std::string& reason) /*virtual*/ void LLMediaDataClient::Responder::result(const LLSD& content) { + mRequest->stopTracking(); + + if(mRequest->isDead()) + { + LL_WARNS("LLMediaDataClient") << "dead request " << *mRequest << LL_ENDL; + return; + } + LL_DEBUGS("LLMediaDataClientResponse") << *mRequest << " result : " << ll_print_sd(content) << LL_ENDL; } @@ -683,15 +628,200 @@ void LLMediaDataClient::Responder::result(const LLSD& content) // ////////////////////////////////////////////////////////////////////////////////////// +void LLObjectMediaDataClient::fetchMedia(LLMediaDataClientObject *object) +{ + // Create a get request and put it in the queue. + enqueue(new RequestGet(object, this)); +} + const char *LLObjectMediaDataClient::getCapabilityName() const { return "ObjectMedia"; } -void LLObjectMediaDataClient::fetchMedia(LLMediaDataClientObject *object) +LLObjectMediaDataClient::request_queue_t *LLObjectMediaDataClient::getQueue() { - // Create a get request and put it in the queue. - enqueue(new RequestGet(object, this)); + return (mCurrentQueueIsTheSortedQueue) ? &mQueue : &mRoundRobinQueue; +} + +void LLObjectMediaDataClient::sortQueue() +{ + if(!mQueue.empty()) + { + // score all elements in the sorted queue. + for(request_queue_t::iterator iter = mQueue.begin(); iter != mQueue.end(); iter++) + { + (*iter)->updateScore(); + } + + // Re-sort the list... + mQueue.sort(compareRequestScores); + + // ...then cull items over the max + U32 size = mQueue.size(); + if (size > mMaxSortedQueueSize) + { + U32 num_to_cull = (size - mMaxSortedQueueSize); + LL_INFOS_ONCE("LLMediaDataClient") << "sorted queue MAXED OUT! Culling " + << num_to_cull << " items" << LL_ENDL; + while (num_to_cull-- > 0) + { + mQueue.back()->markDead(); + mQueue.pop_back(); + } + } + } + +} + +// static +bool LLObjectMediaDataClient::compareRequestScores(const request_ptr_t &o1, const request_ptr_t &o2) +{ + if (o2.isNull()) return true; + if (o1.isNull()) return false; + return ( o1->getScore() > o2->getScore() ); +} + +void LLObjectMediaDataClient::enqueue(Request *request) +{ + if(request->isDead()) + { + LL_DEBUGS("LLMediaDataClient") << "not queueing dead request " << *request << LL_ENDL; + return; + } + + // Invariants: + // new requests always go into the sorted queue. + // + + bool is_new = request->isNew(); + + if(!is_new && (request->getType() == Request::GET)) + { + // For GET requests that are not new, if a matching request is already in the round robin queue, + // in flight, or being retried, leave it at its current position. + request_queue_t::iterator iter = find_matching_request(mRoundRobinQueue, request->getID(), Request::GET); + request_set_t::iterator iter2 = find_matching_request(mUnQueuedRequests, request->getID(), Request::GET); + + if( (iter != mRoundRobinQueue.end()) || (iter2 != mUnQueuedRequests.end()) ) + { + LL_DEBUGS("LLMediaDataClient") << "ALREADY THERE: NOT Queuing request for " << *request << LL_ENDL; + + return; + } + } + + // TODO: should an UPDATE cause pending GET requests for the same object to be removed from the queue? + // IF the update will cause an object update message to be sent out at some point in the future, it probably should. + + // Remove any existing requests of this type for this object + remove_matching_requests(mQueue, request->getID(), request->getType()); + remove_matching_requests(mRoundRobinQueue, request->getID(), request->getType()); + remove_matching_requests(mUnQueuedRequests, request->getID(), request->getType()); + + if (is_new) + { + LL_DEBUGS("LLMediaDataClient") << "Queuing SORTED request for " << *request << LL_ENDL; + + mQueue.push_back(request); + + LL_DEBUGS("LLMediaDataClientQueue") << "SORTED queue:" << mQueue << 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; + } + + LL_DEBUGS("LLMediaDataClient") << "Queuing RR request for " << *request << LL_ENDL; + // Push the request on the pending queue + mRoundRobinQueue.push_back(request); + + LL_DEBUGS("LLMediaDataClientQueue") << "RR queue:" << mRoundRobinQueue << LL_ENDL; + } + // Start the timer if not already running + startQueueTimer(); +} + +bool LLObjectMediaDataClient::canServiceRequest(request_ptr_t request) +{ + if(mCurrentQueueIsTheSortedQueue) + { + if(!request->getObject()->isInterestingEnough()) + { + LL_DEBUGS("LLMediaDataClient") << "Not fetching " << *request << ": not interesting enough" << LL_ENDL; + return false; + } + } + + return true; +}; + +void LLObjectMediaDataClient::swapCurrentQueue() +{ + // Swap + mCurrentQueueIsTheSortedQueue = !mCurrentQueueIsTheSortedQueue; + // If its empty, swap back + if (getQueue()->empty()) + { + mCurrentQueueIsTheSortedQueue = !mCurrentQueueIsTheSortedQueue; + } +} + +bool LLObjectMediaDataClient::isEmpty() const +{ + return mQueue.empty() && mRoundRobinQueue.empty(); +} + +bool LLObjectMediaDataClient::isInQueue(const LLMediaDataClientObject::ptr_t &object) +{ + // First, call parent impl. + if(LLMediaDataClient::isInQueue(object)) + return true; + + if(find_matching_request(mRoundRobinQueue, object->getID()) != mRoundRobinQueue.end()) + return true; + + return false; +} + +void LLObjectMediaDataClient::removeFromQueue(const LLMediaDataClientObject::ptr_t &object) +{ + // First, call parent impl. + LLMediaDataClient::removeFromQueue(object); + + remove_matching_requests(mRoundRobinQueue, object->getID()); +} + +bool LLObjectMediaDataClient::processQueueTimer() +{ + if(isEmpty()) + return true; + + LL_DEBUGS("LLMediaDataClient") << "started, SORTED queue size is: " << mQueue.size() + << ", RR queue size is: " << mRoundRobinQueue.size() << LL_ENDL; + LL_DEBUGS("LLMediaDataClientQueue") << " SORTED queue is: " << mQueue << LL_ENDL; + LL_DEBUGS("LLMediaDataClientQueue") << " RR queue is: " << mRoundRobinQueue << LL_ENDL; + +// purgeDeadRequests(); + + sortQueue(); + + LL_DEBUGS("LLMediaDataClientQueue") << "after sort, SORTED queue is: " << mQueue << LL_ENDL; + + serviceQueue(); + + swapCurrentQueue(); + + LL_DEBUGS("LLMediaDataClient") << "finished, SORTED queue size is: " << mQueue.size() + << ", RR queue size is: " << mRoundRobinQueue.size() << LL_ENDL; + LL_DEBUGS("LLMediaDataClientQueue") << " SORTED queue is: " << mQueue << LL_ENDL; + LL_DEBUGS("LLMediaDataClientQueue") << " RR queue is: " << mRoundRobinQueue << LL_ENDL; + + return isEmpty(); } LLObjectMediaDataClient::RequestGet::RequestGet(LLMediaDataClientObject *obj, LLMediaDataClient *mdc): @@ -754,6 +884,14 @@ LLMediaDataClient::Responder *LLObjectMediaDataClient::RequestUpdate::createResp /*virtual*/ void LLObjectMediaDataClient::Responder::result(const LLSD& content) { + getRequest()->stopTracking(); + + if(getRequest()->isDead()) + { + LL_WARNS("LLMediaDataClient") << "dead request " << *(getRequest()) << LL_ENDL; + return; + } + // This responder is only used for GET requests, not UPDATE. LL_DEBUGS("LLMediaDataClientResponse") << *(getRequest()) << " GET returned: " << ll_print_sd(content) << LL_ENDL; @@ -796,6 +934,50 @@ const char *LLObjectMediaNavigateClient::getCapabilityName() const return "ObjectMediaNavigate"; } +void LLObjectMediaNavigateClient::enqueue(Request *request) +{ + if(request->isDead()) + { + LL_DEBUGS("LLMediaDataClient") << "not queueing dead request " << *request << LL_ENDL; + return; + } + + // If there's already a matching request in the queue, remove it. + request_queue_t::iterator iter = find_matching_request(mQueue, request); + if(iter != mQueue.end()) + { + LL_DEBUGS("LLMediaDataClient") << "removing matching queued request " << (**iter) << LL_ENDL; + mQueue.erase(iter); + } + else + { + request_set_t::iterator set_iter = find_matching_request(mUnQueuedRequests, request); + if(set_iter != mUnQueuedRequests.end()) + { + LL_DEBUGS("LLMediaDataClient") << "removing matching unqueued request " << (**set_iter) << LL_ENDL; + mUnQueuedRequests.erase(set_iter); + } + } + +#if 0 + // Sadly, this doesn't work. It ends up creating a race condition when the user navigates and then hits the "back" button + // where the navigate-back appears to be spurious and doesn't get broadcast. + if(request->getObject()->isCurrentMediaUrl(request->getFace(), request->getURL())) + { + // This navigate request is trying to send the face to the current URL. Drop it. + LL_DEBUGS("LLMediaDataClient") << "dropping spurious request " << (*request) << LL_ENDL; + } + else +#endif + { + LL_DEBUGS("LLMediaDataClient") << "queueing new request " << (*request) << LL_ENDL; + mQueue.push_back(request); + + // Start the timer if not already running + startQueueTimer(); + } +} + void LLObjectMediaNavigateClient::navigate(LLMediaDataClientObject *object, U8 texture_index, const std::string &url) { @@ -806,8 +988,7 @@ void LLObjectMediaNavigateClient::navigate(LLMediaDataClientObject *object, U8 t } LLObjectMediaNavigateClient::RequestNavigate::RequestNavigate(LLMediaDataClientObject *obj, LLMediaDataClient *mdc, U8 texture_index, const std::string &url): - LLMediaDataClient::Request(LLMediaDataClient::Request::NAVIGATE, obj, mdc), - mTextureIndex(texture_index), + LLMediaDataClient::Request(LLMediaDataClient::Request::NAVIGATE, obj, mdc, (S32)texture_index), mURL(url) { } @@ -815,9 +996,9 @@ LLObjectMediaNavigateClient::RequestNavigate::RequestNavigate(LLMediaDataClientO LLSD LLObjectMediaNavigateClient::RequestNavigate::getPayload() const { LLSD result; - result[LLTextureEntry::OBJECT_ID_KEY] = mObject->getID(); + result[LLTextureEntry::OBJECT_ID_KEY] = getID(); result[LLMediaEntry::CURRENT_URL_KEY] = mURL; - result[LLTextureEntry::TEXTURE_INDEX_KEY] = (LLSD::Integer)mTextureIndex; + result[LLTextureEntry::TEXTURE_INDEX_KEY] = (LLSD::Integer)getFace(); return result; } @@ -830,13 +1011,22 @@ LLMediaDataClient::Responder *LLObjectMediaNavigateClient::RequestNavigate::crea /*virtual*/ void LLObjectMediaNavigateClient::Responder::error(U32 status, const std::string& reason) { + getRequest()->stopTracking(); + + if(getRequest()->isDead()) + { + LL_WARNS("LLMediaDataClient") << "dead request " << *(getRequest()) << LL_ENDL; + return; + } + // Bounce back (unless HTTP_SERVICE_UNAVAILABLE, in which case call base // class if (status == HTTP_SERVICE_UNAVAILABLE) { LLMediaDataClient::Responder::error(status, reason); } - else { + else + { // bounce the face back LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error navigating: http code=" << status << LL_ENDL; const LLSD &payload = getRequest()->getPayload(); @@ -848,6 +1038,14 @@ void LLObjectMediaNavigateClient::Responder::error(U32 status, const std::string /*virtual*/ void LLObjectMediaNavigateClient::Responder::result(const LLSD& content) { + getRequest()->stopTracking(); + + if(getRequest()->isDead()) + { + LL_WARNS("LLMediaDataClient") << "dead request " << *(getRequest()) << LL_ENDL; + return; + } + LL_INFOS("LLMediaDataClient") << *(getRequest()) << " NAVIGATE returned " << ll_print_sd(content) << LL_ENDL; if (content.has("error")) @@ -862,14 +1060,17 @@ void LLObjectMediaNavigateClient::Responder::result(const LLSD& content) // bounce the face back getRequest()->getObject()->mediaNavigateBounceBack((LLSD::Integer)payload[LLTextureEntry::TEXTURE_INDEX_KEY]); } - else { + else + { LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error navigating: code=" << error["code"].asString() << ": " << error["message"].asString() << LL_ENDL; } + // XXX Warn user? } - else { - // just do what our superclass does - LLMediaDataClient::Responder::result(content); + else + { + // No action required. + LL_DEBUGS("LLMediaDataClientResponse") << *(getRequest()) << " result : " << ll_print_sd(content) << LL_ENDL; } } diff --git a/indra/newview/llmediadataclient.h b/indra/newview/llmediadataclient.h index 9504a0a2a9..3961442412 100755 --- a/indra/newview/llmediadataclient.h +++ b/indra/newview/llmediadataclient.h @@ -34,7 +34,7 @@ #define LL_LLMEDIADATACLIENT_H #include "llhttpclient.h" -#include <queue> +#include <set> #include "llrefcount.h" #include "llpointer.h" #include "lleventtimer.h" @@ -48,6 +48,8 @@ public: virtual U8 getMediaDataCount() const = 0; // Get the media data at index, as an LLSD virtual LLSD getMediaDataLLSD(U8 index) const = 0; + // Return true if the current URL for the face in the media data matches the specified URL. + virtual bool isCurrentMediaUrl(U8 index, const std::string &url) const = 0; // Get this object's UUID virtual LLUUID getID() const = 0; // Navigate back to previous URL @@ -96,16 +98,16 @@ public: F32 getRetryTimerDelay() const { return mRetryTimerDelay; } // Returns true iff the queue is empty - bool isEmpty() const; + virtual bool isEmpty() const; // Returns true iff the given object is in the queue - bool isInQueue(const LLMediaDataClientObject::ptr_t &object); + virtual 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); + virtual void removeFromQueue(const LLMediaDataClientObject::ptr_t &object); // Called only by the Queue timer and tests (potentially) - bool processQueueTimer(); + virtual bool processQueueTimer(); protected: // Destructor @@ -122,6 +124,8 @@ protected: // and must create the correct type of responder. virtual Responder *createResponder() = 0; + virtual std::string getURL() { return ""; } + enum Type { GET, UPDATE, @@ -131,15 +135,14 @@ protected: protected: // The only way to create one of these is through a subclass. - Request(Type in_type, LLMediaDataClientObject *obj, LLMediaDataClient *mdc); + Request(Type in_type, LLMediaDataClientObject *obj, LLMediaDataClient *mdc, S32 face = -1); public: LLMediaDataClientObject *getObject() const { return mObject; } U32 getNum() const { return mNum; } U32 getRetryCount() const { return mRetryCount; } - void incRetryCount() { mRetryCount++; }; - Type getType() const { return mType; }; - bool isMarkedSent() const { return mMarkedSent; } + void incRetryCount() { mRetryCount++; } + Type getType() const { return mType; } F64 getScore() const { return mScore; } // Note: may return empty string! @@ -153,13 +156,26 @@ protected: F32 getRetryTimerDelay() const; U32 getMaxNumRetries() const; - bool isNew() const { return mObject.notNull() ? mObject->isNew() : false; } - bool isObjectValid() const { return mObject.notNull() ? (!mObject->isDead()) : false; } - void markSent(bool flag); + bool isObjectValid() const { return mObject.notNull() && (!mObject->isDead()); } + bool isNew() const { return isObjectValid() && mObject->isNew(); } void updateScore(); + void markDead(); + bool isDead(); + void startTracking(); + void stopTracking(); + friend std::ostream& operator<<(std::ostream &s, const Request &q); - + + const LLUUID &getID() const { return mObjectID; } + S32 getFace() const { return mFace; } + + bool isMatch (const Request* other, Type match_type = ANY) const + { + return ((match_type == ANY) || (mType == other->mType)) && + (mFace == other->mFace) && + (mObjectID == other->mObjectID); + } protected: LLMediaDataClientObject::ptr_t mObject; private: @@ -169,7 +185,9 @@ protected: static U32 sNum; U32 mRetryCount; F64 mScore; - bool mMarkedSent; + + LLUUID mObjectID; + S32 mFace; // Back pointer to the MDC...not a ref! LLMediaDataClient *mMDC; @@ -189,39 +207,63 @@ protected: request_ptr_t &getRequest() { return mRequest; } private: + request_ptr_t mRequest; + }; - class RetryTimer : public LLEventTimer - { - public: - RetryTimer(F32 time, Responder *); - virtual BOOL tick(); - private: - // back-pointer - boost::intrusive_ptr<Responder> mResponder; - }; - + class RetryTimer : public LLEventTimer + { + public: + RetryTimer(F32 time, request_ptr_t); + virtual BOOL tick(); + private: + // back-pointer request_ptr_t mRequest; }; + protected: + typedef std::list<request_ptr_t> request_queue_t; + typedef std::set<request_ptr_t> request_set_t; // Subclasses must override to return a cap name virtual const char *getCapabilityName() const = 0; + + // Puts the request into a queue, appropriately handling duplicates, etc. + virtual void enqueue(Request*) = 0; - virtual bool request_needs_purge(request_ptr_t request); - virtual void sortQueue(); virtual void serviceQueue(); - virtual void enqueue(Request*); + virtual request_queue_t *getQueue() { return &mQueue; }; + + // Gets the next request, removing it from the queue + virtual request_ptr_t dequeue(); -private: - typedef std::list<request_ptr_t> request_queue_t; + virtual bool canServiceRequest(request_ptr_t request) { return true; }; + + // Returns a request to the head of the queue (should only be used for requests that came from dequeue + virtual void pushBack(request_ptr_t 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); + void trackRequest(request_ptr_t request); + void stopTrackingRequest(request_ptr_t request); + + request_queue_t mQueue; + + const F32 mQueueTimerDelay; + const F32 mRetryTimerDelay; + const U32 mMaxNumRetries; + const U32 mMaxSortedQueueSize; + const U32 mMaxRoundRobinQueueSize; + + // Set for keeping track of requests that aren't in either queue. This includes: + // Requests that have been sent and are awaiting a response (pointer held by the Responder) + // Requests that are waiting for their retry timers to fire (pointer held by the retry timer) + request_set_t mUnQueuedRequests; + + void startQueueTimer(); + void stopQueueTimer(); + +private: - // Comparator for sorting - static bool compareRequests(const request_ptr_t &o1, const request_ptr_t &o2); static F64 getObjectScore(const LLMediaDataClientObject::ptr_t &obj); friend std::ostream& operator<<(std::ostream &s, const Request &q); @@ -237,24 +279,10 @@ private: LLPointer<LLMediaDataClient> mMDC; }; - 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 mSortedQueue; - request_queue_t mRoundRobinQueue; - bool mCurrentQueueIsTheSortedQueue; + }; @@ -262,14 +290,15 @@ private: class LLObjectMediaDataClient : public LLMediaDataClient { public: + LOG_CLASS(LLObjectMediaDataClient); LLObjectMediaDataClient(F32 queue_timer_delay = QUEUE_TIMER_DELAY, F32 retry_timer_delay = UNAVAILABLE_RETRY_TIMER_DELAY, 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) + : LLMediaDataClient(queue_timer_delay, retry_timer_delay, max_retries), + mCurrentQueueIsTheSortedQueue(true) {} - virtual ~LLObjectMediaDataClient() {} void fetchMedia(LLMediaDataClientObject *object); void updateMedia(LLMediaDataClientObject *object); @@ -289,11 +318,29 @@ public: /*virtual*/ LLSD getPayload() const; /*virtual*/ Responder *createResponder(); }; + + // Returns true iff the queue is empty + virtual bool isEmpty() const; + + // Returns true iff the given object is in the queue + virtual bool isInQueue(const LLMediaDataClientObject::ptr_t &object); + // Remove the given object from the queue. Returns true iff the given object is removed. + virtual void removeFromQueue(const LLMediaDataClientObject::ptr_t &object); + + virtual bool processQueueTimer(); + + virtual bool canServiceRequest(request_ptr_t request); + protected: // Subclasses must override to return a cap name virtual const char *getCapabilityName() const; - + + virtual request_queue_t *getQueue(); + + // Puts the request into the appropriate queue + virtual void enqueue(Request*); + class Responder : public LLMediaDataClient::Responder { public: @@ -301,6 +348,16 @@ protected: : LLMediaDataClient::Responder(request) {} virtual void result(const LLSD &content); }; +private: + // The Get/Update data client needs a second queue to avoid object updates starving load-ins. + void swapCurrentQueue(); + + request_queue_t mRoundRobinQueue; + bool mCurrentQueueIsTheSortedQueue; + + // Comparator for sorting + static bool compareRequestScores(const request_ptr_t &o1, const request_ptr_t &o2); + void sortQueue(); }; @@ -308,6 +365,7 @@ protected: class LLObjectMediaNavigateClient : public LLMediaDataClient { public: + LOG_CLASS(LLObjectMediaNavigateClient); // NOTE: from llmediaservice.h static const int ERROR_PERMISSION_DENIED_CODE = 8002; @@ -318,18 +376,20 @@ public: U32 max_round_robin_queue_size = MAX_ROUND_ROBIN_QUEUE_SIZE) : LLMediaDataClient(queue_timer_delay, retry_timer_delay, max_retries) {} - virtual ~LLObjectMediaNavigateClient() {} void navigate(LLMediaDataClientObject *object, U8 texture_index, const std::string &url); + // Puts the request into the appropriate queue + virtual void enqueue(Request*); + class RequestNavigate: public Request { public: RequestNavigate(LLMediaDataClientObject *obj, LLMediaDataClient *mdc, U8 texture_index, const std::string &url); /*virtual*/ LLSD getPayload() const; /*virtual*/ Responder *createResponder(); + /*virtual*/ std::string getURL() { return mURL; } private: - U8 mTextureIndex; std::string mURL; }; diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index c838e9a28d..00d67a4ea9 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -93,8 +93,14 @@ static LLFastTimer::DeclareTimer FTM_GEN_VOLUME("Generate Volumes"); class LLMediaDataClientObjectImpl : public LLMediaDataClientObject { public: - LLMediaDataClientObjectImpl(LLVOVolume *obj, bool isNew) : mObject(obj), mNew(isNew) {} - LLMediaDataClientObjectImpl() { mObject = NULL; } + LLMediaDataClientObjectImpl(LLVOVolume *obj, bool isNew) : mObject(obj), mNew(isNew) + { + mObject->addMDCImpl(); + } + ~LLMediaDataClientObjectImpl() + { + mObject->removeMDCImpl(); + } virtual U8 getMediaDataCount() const { return mObject->getNumTEs(); } @@ -119,6 +125,18 @@ public: } return result; } + virtual bool isCurrentMediaUrl(U8 index, const std::string &url) const + { + LLTextureEntry *te = mObject->getTE(index); + if (te) + { + if (te->getMediaData()) + { + return (te->getMediaData()->getCurrentURL() == url); + } + } + return url.empty(); + } virtual LLUUID getID() const { return mObject->getID(); } @@ -193,6 +211,7 @@ LLVOVolume::LLVOVolume(const LLUUID &id, const LLPCode pcode, LLViewerRegion *re mMediaImplList.resize(getNumTEs()); mLastFetchedMediaVersion = -1; mIndexInTex = 0; + mMDCImplCount = 0; } LLVOVolume::~LLVOVolume() @@ -218,9 +237,12 @@ void LLVOVolume::markDead() { if (!mDead) { - LLMediaDataClientObject::ptr_t obj = new LLMediaDataClientObjectImpl(const_cast<LLVOVolume*>(this), false); - if (sObjectMediaClient) sObjectMediaClient->removeFromQueue(obj); - if (sObjectMediaNavigateClient) sObjectMediaNavigateClient->removeFromQueue(obj); + if(getMDCImplCount() > 0) + { + LLMediaDataClientObject::ptr_t obj = new LLMediaDataClientObjectImpl(const_cast<LLVOVolume*>(this), false); + if (sObjectMediaClient) sObjectMediaClient->removeFromQueue(obj); + if (sObjectMediaNavigateClient) sObjectMediaNavigateClient->removeFromQueue(obj); + } // Detach all media impls from this object for(U32 i = 0 ; i < mMediaImplList.size() ; i++) diff --git a/indra/newview/llvovolume.h b/indra/newview/llvovolume.h index fbae011ffc..1bd6a0fafe 100644 --- a/indra/newview/llvovolume.h +++ b/indra/newview/llvovolume.h @@ -274,6 +274,10 @@ public: // Returns the "last fetched" media version, or -1 if not fetched yet S32 getLastFetchedMediaVersion() const { return mLastFetchedMediaVersion; } + + void addMDCImpl() { ++mMDCImplCount; } + void removeMDCImpl() { --mMDCImplCount; } + S32 getMDCImplCount() { return mMDCImplCount; } protected: S32 computeLODDetail(F32 distance, F32 radius); @@ -307,6 +311,7 @@ private: media_list_t mMediaImplList; S32 mLastFetchedMediaVersion; // as fetched from the server, starts as -1 S32 mIndexInTex; + S32 mMDCImplCount; // statics public: static F32 sLODSlopDistanceFactor;// Changing this to zero, effectively disables the LOD transition slop diff --git a/indra/newview/tests/llmediadataclient_test.cpp b/indra/newview/tests/llmediadataclient_test.cpp index d73ea24768..05e178653b 100644 --- a/indra/newview/tests/llmediadataclient_test.cpp +++ b/indra/newview/tests/llmediadataclient_test.cpp @@ -70,8 +70,8 @@ #define MEDIA_DATA "\ <array> \ -<string>foo</string> \ -<string>bar</string> \ +<string>http://foo.example.com</string> \ +<string>http://bar.example.com</string> \ <string>baz</string> \ </array>" @@ -167,6 +167,8 @@ public: { return mRep["media_data"].size(); } virtual LLSD getMediaDataLLSD(U8 index) const { return mRep["media_data"][(LLSD::Integer)index]; } + virtual bool isCurrentMediaUrl(U8 index, const std::string &url) const + { return (mRep["media_data"][(LLSD::Integer)index].asString() == url); } virtual LLUUID getID() const { return mRep["uuid"]; } virtual void mediaNavigateBounceBack(U8 index) @@ -567,20 +569,29 @@ namespace tut mdc->fetchMedia(o2); mdc->fetchMedia(o3); mdc->fetchMedia(o4); + + ensure("is in queue 1", mdc->isInQueue(o1)); + ensure("is in queue 2", mdc->isInQueue(o2)); + ensure("is in queue 3", mdc->isInQueue(o3)); + ensure("is in queue 4", mdc->isInQueue(o4)); + ensure("post records", gPostRecords->size(), 0); - // and mark the second and fourth ones dead. + // and mark the second and fourth ones dead. Call removeFromQueue when marking dead, since this is what LLVOVolume will do. dynamic_cast<LLMediaDataClientObjectTest*>(static_cast<LLMediaDataClientObject*>(o2))->markDead(); + mdc->removeFromQueue(o2); dynamic_cast<LLMediaDataClientObjectTest*>(static_cast<LLMediaDataClientObject*>(o4))->markDead(); + mdc->removeFromQueue(o4); + // The removeFromQueue calls should remove the second and fourth ones ensure("is in queue 1", mdc->isInQueue(o1)); - ensure("is in queue 2", mdc->isInQueue(o2)); + ensure("is not in queue 2", !mdc->isInQueue(o2)); ensure("is in queue 3", mdc->isInQueue(o3)); - ensure("is in queue 4", mdc->isInQueue(o4)); + ensure("is not in queue 4", !mdc->isInQueue(o4)); ensure("post records", gPostRecords->size(), 0); ::pump_timers(); - // The first tick should remove the second and fourth ones, and process the first one + // The first tick should process the first item ensure("is not in queue 1", !mdc->isInQueue(o1)); ensure("is not in queue 2", !mdc->isInQueue(o2)); ensure("is in queue 3", mdc->isInQueue(o3)); @@ -701,7 +712,7 @@ namespace tut // 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 + // 2, 3, 1, 4 mdc->fetchMedia(o1); mdc->fetchMedia(o2); mdc->fetchMedia(o3); @@ -720,8 +731,8 @@ namespace tut ++tick_num; // 1 The first tick should remove object 2 - ensure(STR(tick_num) + ". is not in queue 2", !mdc->isInQueue(o2)); ensure(STR(tick_num) + ". is in queue 1", mdc->isInQueue(o1)); + ensure(STR(tick_num) + ". is not in queue 2", !mdc->isInQueue(o2)); ensure(STR(tick_num) + ". is in queue 3", mdc->isInQueue(o3)); ensure(STR(tick_num) + ". is in queue 4", mdc->isInQueue(o4)); ensure(STR(tick_num) + ". post records", gPostRecords->size(), 1); @@ -730,22 +741,21 @@ namespace tut ::pump_timers(); ++tick_num; - // 2 The second tick should send object 4, but it will still be - // "in the queue" - ensure(STR(tick_num) + ". is not in queue 2", !mdc->isInQueue(o2)); + // 2 The second tick should send object 3 ensure(STR(tick_num) + ". is in queue 1", mdc->isInQueue(o1)); - ensure(STR(tick_num) + ". is in queue 3", mdc->isInQueue(o3)); + ensure(STR(tick_num) + ". is not in queue 2", !mdc->isInQueue(o2)); + ensure(STR(tick_num) + ". is not in queue 3", !mdc->isInQueue(o3)); ensure(STR(tick_num) + ". is in queue 4", mdc->isInQueue(o4)); ensure(STR(tick_num) + ". post records", gPostRecords->size(), 2); - ensure(STR(tick_num) + ". post object id", (*gPostRecords)[1]["body"][LLTextureEntry::OBJECT_ID_KEY].asUUID(), LLUUID(VALID_OBJECT_ID_4)); + ensure(STR(tick_num) + ". post object id", (*gPostRecords)[1]["body"][LLTextureEntry::OBJECT_ID_KEY].asUUID(), LLUUID(VALID_OBJECT_ID_3)); ::pump_timers(); ++tick_num; // 3 The third tick should remove object 1 - ensure(STR(tick_num) + ". is not in queue 2", !mdc->isInQueue(o2)); ensure(STR(tick_num) + ". is not in queue 1", !mdc->isInQueue(o1)); - ensure(STR(tick_num) + ". is in queue 3", mdc->isInQueue(o3)); + ensure(STR(tick_num) + ". is not in queue 2", !mdc->isInQueue(o2)); + ensure(STR(tick_num) + ". is not in queue 3", !mdc->isInQueue(o3)); ensure(STR(tick_num) + ". is in queue 4", mdc->isInQueue(o4)); ensure(STR(tick_num) + ". post records", gPostRecords->size(), 3); ensure(STR(tick_num) + ". post object id", (*gPostRecords)[2]["body"][LLTextureEntry::OBJECT_ID_KEY].asUUID(), LLUUID(VALID_OBJECT_ID_1)); @@ -753,22 +763,20 @@ namespace tut ::pump_timers(); ++tick_num; - // 4 The fourth tick should send object 3, but it will still be - // "in the queue" - ensure(STR(tick_num) + ". is not in queue 2", !mdc->isInQueue(o2)); + // 4 The fourth tick should send object 4 ensure(STR(tick_num) + ". is not in queue 1", !mdc->isInQueue(o1)); - ensure(STR(tick_num) + ". is in queue 3", mdc->isInQueue(o3)); - ensure(STR(tick_num) + ". is in queue 4", mdc->isInQueue(o4)); + ensure(STR(tick_num) + ". is not in queue 2", !mdc->isInQueue(o2)); + ensure(STR(tick_num) + ". is not in queue 3", !mdc->isInQueue(o3)); + ensure(STR(tick_num) + ". is not in queue 4", !mdc->isInQueue(o4)); ensure(STR(tick_num) + ". post records", gPostRecords->size(), 4); - ensure(STR(tick_num) + ". post object id", (*gPostRecords)[3]["body"][LLTextureEntry::OBJECT_ID_KEY].asUUID(), LLUUID(VALID_OBJECT_ID_3)); + ensure(STR(tick_num) + ". post object id", (*gPostRecords)[3]["body"][LLTextureEntry::OBJECT_ID_KEY].asUUID(), LLUUID(VALID_OBJECT_ID_4)); ::pump_timers(); ++tick_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(tick_num) + ". is not in queue 2", !mdc->isInQueue(o2)); + // 5 The fifth tick should not change the state of anything. ensure(STR(tick_num) + ". is not in queue 1", !mdc->isInQueue(o1)); + ensure(STR(tick_num) + ". is not in queue 2", !mdc->isInQueue(o2)); ensure(STR(tick_num) + ". is not in queue 3", !mdc->isInQueue(o3)); ensure(STR(tick_num) + ". is not in queue 4", !mdc->isInQueue(o4)); ensure(STR(tick_num) + ". post records", gPostRecords->size(), 4); @@ -918,7 +926,7 @@ namespace tut // But, we need to clear the queue, or else we won't destroy MDC... // this is a strange interplay between the queue timer and the MDC - ensure("o2 couldn't be removed from queue", mdc->removeFromQueue(o2)); + mdc->removeFromQueue(o2); // tick ::pump_timers(); } @@ -927,4 +935,41 @@ namespace tut ensure("refcount of o3", o3->getNumRefs(), 1); ensure("refcount of o4", o4->getNumRefs(), 1); } + + template<> template<> + void mediadataclient_object_t::test<13>() + { + // + // Test supression of redundant navigates. + // + LOG_TEST(13); + + LLMediaDataClientObject::ptr_t o1 = new LLMediaDataClientObjectTest(_DATA(VALID_OBJECT_ID_1,"1.0","true")); + { + LLPointer<LLObjectMediaNavigateClient> mdc = new LLObjectMediaNavigateClient(NO_PERIOD,NO_PERIOD); + const char *TEST_URL = "http://foo.example.com"; + const char *TEST_URL_2 = "http://example.com"; + mdc->navigate(o1, 0, TEST_URL); + mdc->navigate(o1, 1, TEST_URL); + mdc->navigate(o1, 0, TEST_URL_2); + mdc->navigate(o1, 1, TEST_URL_2); + + // This should add two requests to the queue, one for face 0 of the object and one for face 1. + + ensure("before pump: 1 is in queue", mdc->isInQueue(o1)); + + ::pump_timers(); + + ensure("after first pump: 1 is in queue", mdc->isInQueue(o1)); + + ::pump_timers(); + + ensure("after second pump: 1 is not in queue", !mdc->isInQueue(o1)); + + ensure("first post has correct url", (*gPostRecords)[0]["body"][LLMediaEntry::CURRENT_URL_KEY].asString(), std::string(TEST_URL_2)); + ensure("second post has correct url", (*gPostRecords)[1]["body"][LLMediaEntry::CURRENT_URL_KEY].asString(), std::string(TEST_URL_2)); + + } + } + } |