438 lines
16 KiB
C++
438 lines
16 KiB
C++
/**
|
|
* @file llavatarrenderinfoaccountant.cpp
|
|
* @author Dave Simmons
|
|
* @date 2013-02-28
|
|
* @brief
|
|
*
|
|
* $LicenseInfo:firstyear=2013&license=viewerlgpl$
|
|
* Second Life Viewer Source Code
|
|
* Copyright (C) 2013, Linden Research, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation;
|
|
* version 2.1 of the License only.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
// Precompiled header
|
|
#include "llviewerprecompiledheaders.h"
|
|
// associated header
|
|
#include "llavatarrenderinfoaccountant.h"
|
|
#include "llavatarrendernotifier.h"
|
|
// STL headers
|
|
// std headers
|
|
// external library headers
|
|
// other Linden headers
|
|
#include "llcharacter.h"
|
|
#include "lltimer.h"
|
|
#include "llviewercontrol.h"
|
|
#include "llviewermenu.h"
|
|
#include "llviewerobjectlist.h"
|
|
#include "llviewerregion.h"
|
|
#include "llvoavatar.h"
|
|
#include "llworld.h"
|
|
#include "llhttpsdhandler.h"
|
|
#include "httpheaders.h"
|
|
#include "httpoptions.h"
|
|
#include "llcorehttputil.h"
|
|
|
|
static const std::string KEY_AGENTS = "agents"; // map
|
|
static const std::string KEY_WEIGHT = "weight"; // integer
|
|
static const std::string KEY_TOO_COMPLEX = "tooComplex"; // bool
|
|
static const std::string KEY_OVER_COMPLEXITY_LIMIT = "overlimit"; // integer
|
|
static const std::string KEY_REPORTING_COMPLEXITY_LIMIT = "reportinglimit"; // integer
|
|
|
|
static const std::string KEY_IDENTIFIER = "identifier";
|
|
static const std::string KEY_MESSAGE = "message";
|
|
static const std::string KEY_ERROR = "error";
|
|
|
|
static const F32 SECS_BETWEEN_REGION_SCANS = 5.f; // Scan the region list every 5 seconds
|
|
static const F32 SECS_BETWEEN_REGION_REQUEST = 15.0; // Look for new avs every 15 seconds
|
|
static const F32 SECS_BETWEEN_REGION_REPORTS = 60.0; // Update each region every 60 seconds
|
|
|
|
|
|
//=========================================================================
|
|
LLAvatarRenderInfoAccountant::LLAvatarRenderInfoAccountant()
|
|
{
|
|
}
|
|
|
|
LLAvatarRenderInfoAccountant::~LLAvatarRenderInfoAccountant()
|
|
{
|
|
}
|
|
|
|
|
|
void LLAvatarRenderInfoAccountant::avatarRenderInfoGetCoro(std::string url, U64 regionHandle)
|
|
{
|
|
LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
|
|
LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
|
|
httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("AvatarRenderInfoAccountant", httpPolicy));
|
|
LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
|
|
LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions);
|
|
|
|
// Going to request each 15 seconds either way, so don't wait
|
|
// too long and don't repeat
|
|
httpOpts->setRetries(0);
|
|
httpOpts->setTimeout((unsigned int)SECS_BETWEEN_REGION_REQUEST);
|
|
|
|
LLSD result = httpAdapter->getAndSuspend(httpRequest, url, httpOpts);
|
|
|
|
LLWorld *world_inst = LLWorld::getInstance();
|
|
if (!world_inst)
|
|
{
|
|
LL_WARNS("AvatarRenderInfoAccountant") << "Avatar render weight info received but world no longer exists "
|
|
<< regionHandle << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
LLViewerRegion * regionp = world_inst->getRegionFromHandle(regionHandle);
|
|
if (!regionp)
|
|
{
|
|
LL_WARNS("AvatarRenderInfoAccountant") << "Avatar render weight info received but region not found for "
|
|
<< regionHandle << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
regionp->getRenderInfoRequestTimer().resetWithExpiry(SECS_BETWEEN_REGION_REQUEST);
|
|
|
|
LLSD httpResults = result["http_result"];
|
|
LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
|
|
|
|
if (!status)
|
|
{
|
|
LL_WARNS("AvatarRenderInfoAccountant") << "HTTP status, " << status.toTerseString() << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
if (result.has(KEY_AGENTS))
|
|
{
|
|
const LLSD & agents = result[KEY_AGENTS];
|
|
if (agents.isMap())
|
|
{
|
|
for (LLSD::map_const_iterator agent_iter = agents.beginMap();
|
|
agent_iter != agents.endMap();
|
|
agent_iter++
|
|
)
|
|
{
|
|
LLUUID target_agent_id = LLUUID(agent_iter->first);
|
|
LLViewerObject* vobjp = gObjectList.findObject(target_agent_id);
|
|
LLVOAvatar *avatarp = NULL;
|
|
if (vobjp)
|
|
{
|
|
avatarp = vobjp->asAvatar();
|
|
}
|
|
if (avatarp && !avatarp->isControlAvatar())
|
|
{
|
|
const LLSD & agent_info_map = agent_iter->second;
|
|
if (agent_info_map.isMap())
|
|
{
|
|
LL_DEBUGS("AvatarRenderInfo") << " Agent " << target_agent_id
|
|
<< ": " << agent_info_map << LL_ENDL;
|
|
|
|
if (agent_info_map.has(KEY_WEIGHT))
|
|
{
|
|
avatarp->setReportedVisualComplexity(agent_info_map[KEY_WEIGHT].asInteger());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS("AvatarRenderInfo") << "agent entry invalid"
|
|
<< " agent " << target_agent_id
|
|
<< " map " << agent_info_map
|
|
<< LL_ENDL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LL_DEBUGS("AvatarRenderInfo") << "Unknown agent " << target_agent_id << LL_ENDL;
|
|
}
|
|
} // for agent_iter
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS("AvatarRenderInfo") << "malformed get response '" << KEY_AGENTS << "' is not map" << LL_ENDL;
|
|
}
|
|
} // has "agents"
|
|
else
|
|
{
|
|
LL_INFOS("AvatarRenderInfo") << "no '"<< KEY_AGENTS << "' key in get response" << LL_ENDL;
|
|
}
|
|
|
|
if ( result.has(KEY_REPORTING_COMPLEXITY_LIMIT)
|
|
&& result.has(KEY_OVER_COMPLEXITY_LIMIT))
|
|
{
|
|
U32 reporting = result[KEY_REPORTING_COMPLEXITY_LIMIT].asInteger();
|
|
U32 overlimit = result[KEY_OVER_COMPLEXITY_LIMIT].asInteger();
|
|
|
|
LL_DEBUGS("AvatarRenderInfo") << "complexity limit: "<<reporting<<" reporting, "<<overlimit<<" over limit"<<LL_ENDL;
|
|
|
|
LLAvatarRenderNotifier::getInstance()->updateNotificationRegion(reporting, overlimit);
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS("AvatarRenderInfo")
|
|
<< "response is missing either '" << KEY_REPORTING_COMPLEXITY_LIMIT
|
|
<< "' or '" << KEY_OVER_COMPLEXITY_LIMIT << "'"
|
|
<< LL_ENDL;
|
|
}
|
|
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
void LLAvatarRenderInfoAccountant::avatarRenderInfoReportCoro(std::string url, U64 regionHandle)
|
|
{
|
|
LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
|
|
LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
|
|
httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("AvatarRenderInfoAccountant", httpPolicy));
|
|
LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
|
|
LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions);
|
|
|
|
// Going to request each 60+ seconds, timeout is 30s.
|
|
// Don't repeat too often, will be sending newer data soon
|
|
httpOpts->setRetries(1);
|
|
|
|
LLWorld *world_inst = LLWorld::getInstance();
|
|
if (!world_inst)
|
|
{
|
|
LL_WARNS("AvatarRenderInfoAccountant") << "Avatar render weight calculation but world no longer exists "
|
|
<< regionHandle << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
LLViewerRegion * regionp = world_inst->getRegionFromHandle(regionHandle);
|
|
if (!regionp)
|
|
{
|
|
LL_WARNS("AvatarRenderInfoAccountant") << "Avatar render weight calculation but region not found for "
|
|
<< regionHandle << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
LL_DEBUGS("AvatarRenderInfoAccountant")
|
|
<< "Sending avatar render info for region " << regionp->getName()
|
|
<< " to " << url << LL_ENDL;
|
|
|
|
U32 num_avs = 0;
|
|
// Build the render info to POST to the region
|
|
LLSD agents = LLSD::emptyMap();
|
|
|
|
std::vector<LLCharacter*>::iterator iter = LLCharacter::sInstances.begin();
|
|
while( iter != LLCharacter::sInstances.end() )
|
|
{
|
|
LLVOAvatar* avatar = dynamic_cast<LLVOAvatar*>(*iter);
|
|
if (avatar &&
|
|
avatar->getRezzedStatus() >= 2 && // Mostly rezzed (maybe without baked textures downloaded)
|
|
!avatar->isDead() && // Not dead yet
|
|
!avatar->isControlAvatar() && // Not part of an animated object
|
|
avatar->getObjectHost() == regionp->getHost()) // Ensure it's on the same region
|
|
{
|
|
LLSD info = LLSD::emptyMap();
|
|
U32 avatar_complexity = avatar->getVisualComplexity();
|
|
if (avatar_complexity > 0)
|
|
{
|
|
// the weight/complexity is unsigned, but LLSD only stores signed integers,
|
|
// so if it's over that (which would be ridiculously high), just store the maximum signed int value
|
|
info[KEY_WEIGHT] = (S32)(avatar_complexity < S32_MAX ? avatar_complexity : S32_MAX);
|
|
info[KEY_TOO_COMPLEX] = LLSD::Boolean(avatar->isTooComplex());
|
|
agents[avatar->getID().asString()] = info;
|
|
|
|
LL_DEBUGS("AvatarRenderInfo") << "Sending avatar render info for " << avatar->getID()
|
|
<< ": " << info << LL_ENDL;
|
|
num_avs++;
|
|
}
|
|
}
|
|
iter++;
|
|
}
|
|
|
|
// Reset this regions timer, moving to longer intervals if there are lots of avatars around
|
|
regionp->getRenderInfoReportTimer().resetWithExpiry(SECS_BETWEEN_REGION_REPORTS + (2.f * num_avs));
|
|
|
|
if (num_avs == 0)
|
|
return; // nothing to report
|
|
|
|
LLSD report = LLSD::emptyMap();
|
|
report[KEY_AGENTS] = agents;
|
|
|
|
regionp = NULL;
|
|
world_inst = NULL;
|
|
LLSD result = httpAdapter->postAndSuspend(httpRequest, url, report, httpOpts);
|
|
|
|
world_inst = LLWorld::getInstance();
|
|
if (!world_inst)
|
|
{
|
|
LL_WARNS("AvatarRenderInfoAccountant") << "Avatar render weight POST result but world no longer exists "
|
|
<< regionHandle << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
regionp = world_inst->getRegionFromHandle(regionHandle);
|
|
if (!regionp)
|
|
{
|
|
LL_INFOS("AvatarRenderInfoAccountant") << "Avatar render weight POST result received but region not found for "
|
|
<< regionHandle << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
LLSD httpResults = result["http_result"];
|
|
LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
|
|
if (!status)
|
|
{
|
|
LL_WARNS("AvatarRenderInfoAccountant") << "HTTP status, " << status.toTerseString() << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
if (result.isMap())
|
|
{
|
|
if (result.has(KEY_ERROR))
|
|
{
|
|
const LLSD & error = result[KEY_ERROR];
|
|
LL_WARNS("AvatarRenderInfoAccountant") << "POST error: "
|
|
<< error[KEY_IDENTIFIER]
|
|
<< ": " << error[KEY_MESSAGE]
|
|
<< " from region " << regionp->getName()
|
|
<< LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
LL_DEBUGS("AvatarRenderInfoAccountant")
|
|
<< "POST result for region " << regionp->getName()
|
|
<< ": " << result
|
|
<< LL_ENDL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS("AvatarRenderInfoAccountant") << "Malformed POST response from region '" << regionp->getName()
|
|
<< LL_ENDL;
|
|
}
|
|
}
|
|
|
|
// Send request for avatar weights in one region
|
|
// called when the mRenderInfoScanTimer expires (forced when entering a new region)
|
|
void LLAvatarRenderInfoAccountant::sendRenderInfoToRegion(LLViewerRegion * regionp)
|
|
{
|
|
std::string url = regionp->getCapability("AvatarRenderInfo");
|
|
if ( !url.empty() // we have the capability
|
|
&& regionp->getRenderInfoReportTimer().hasExpired() // Time to make request)
|
|
)
|
|
{
|
|
// make sure we won't re-report, coro will update timer with correct time later
|
|
regionp->getRenderInfoReportTimer().resetWithExpiry(SECS_BETWEEN_REGION_REPORTS);
|
|
|
|
try
|
|
{
|
|
std::string coroname =
|
|
LLCoros::instance().launch("LLAvatarRenderInfoAccountant::avatarRenderInfoReportCoro",
|
|
boost::bind(&LLAvatarRenderInfoAccountant::avatarRenderInfoReportCoro, url, regionp->getHandle()));
|
|
}
|
|
catch (std::bad_alloc&)
|
|
{
|
|
LLError::LLUserWarningMsg::showOutOfMemory();
|
|
LL_ERRS() << "LLCoros::launch() allocation failure" << LL_ENDL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Send request for avatar weights in one region
|
|
// called when the mRenderInfoScanTimer expires (forced when entering a new region)
|
|
void LLAvatarRenderInfoAccountant::getRenderInfoFromRegion(LLViewerRegion * regionp)
|
|
{
|
|
std::string url = regionp->getCapability("AvatarRenderInfo");
|
|
if ( !url.empty()
|
|
&& regionp->getRenderInfoRequestTimer().hasExpired()
|
|
)
|
|
{
|
|
LL_DEBUGS("AvatarRenderInfo")
|
|
<< "Requesting avatar render info for region " << regionp->getName()
|
|
<< " from " << url
|
|
<< LL_ENDL;
|
|
|
|
// make sure we won't re-request, coro will update timer with correct time later
|
|
regionp->getRenderInfoRequestTimer().resetWithExpiry(SECS_BETWEEN_REGION_REQUEST);
|
|
|
|
try
|
|
{
|
|
// First send a request to get the latest data
|
|
std::string coroname =
|
|
LLCoros::instance().launch("LLAvatarRenderInfoAccountant::avatarRenderInfoGetCoro",
|
|
boost::bind(&LLAvatarRenderInfoAccountant::avatarRenderInfoGetCoro, url, regionp->getHandle()));
|
|
}
|
|
catch (std::bad_alloc&)
|
|
{
|
|
LLError::LLUserWarningMsg::showOutOfMemory();
|
|
LL_ERRS() << "LLCoros::launch() allocation failure" << LL_ENDL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Called every frame - send render weight requests to every region
|
|
void LLAvatarRenderInfoAccountant::idle()
|
|
{
|
|
if (mRenderInfoScanTimer.hasExpired() && !LLApp::isExiting())
|
|
{
|
|
LL_DEBUGS("AvatarRenderInfo") << "Scanning regions for render info updates"
|
|
<< LL_ENDL;
|
|
|
|
// Check all regions
|
|
for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin();
|
|
iter != LLWorld::getInstance()->getRegionList().end();
|
|
++iter)
|
|
{
|
|
LLViewerRegion* regionp = *iter;
|
|
if ( regionp
|
|
&& regionp->isAlive()
|
|
&& regionp->capabilitiesReceived())
|
|
{
|
|
// each of these is further governed by and resets its own timer
|
|
// Note: We can have multiple regions, each launches up to two coroutines,
|
|
// it likely is expensive
|
|
sendRenderInfoToRegion(regionp);
|
|
getRenderInfoFromRegion(regionp);
|
|
}
|
|
}
|
|
|
|
// We scanned all the regions, reset the request timer.
|
|
mRenderInfoScanTimer.resetWithExpiry(SECS_BETWEEN_REGION_SCANS);
|
|
}
|
|
}
|
|
|
|
void LLAvatarRenderInfoAccountant::resetRenderInfoScanTimer()
|
|
{
|
|
// this will force the next frame to rescan
|
|
mRenderInfoScanTimer.reset();
|
|
}
|
|
|
|
// static
|
|
// Called via LLViewerRegion::setCapabilitiesReceived() boost signals when the capabilities
|
|
// are returned for a new LLViewerRegion, and is the earliest time to get render info
|
|
void LLAvatarRenderInfoAccountant::scanNewRegion(const LLUUID& region_id)
|
|
{
|
|
LL_DEBUGS("AvatarRenderInfo") << region_id << LL_ENDL;
|
|
|
|
// Reset the global timer so it will scan regions on the next call to ::idle
|
|
LLAvatarRenderInfoAccountant::getInstance()->resetRenderInfoScanTimer();
|
|
|
|
LLViewerRegion* regionp = LLWorld::instance().getRegionFromID(region_id);
|
|
if (regionp)
|
|
{ // Reset the region's timers so we will:
|
|
// * request render info from it immediately
|
|
// * report on the following scan
|
|
regionp->getRenderInfoRequestTimer().reset();
|
|
regionp->getRenderInfoReportTimer().resetWithExpiry(SECS_BETWEEN_REGION_SCANS);
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS("AvatarRenderInfo") << "unable to resolve region "<<region_id<<LL_ENDL;
|
|
}
|
|
}
|