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)); + +		}		 +	} +	  } | 
