/** 
 * @file llviewerregion.cpp
 * @brief Implementation of the LLViewerRegion class.
 *
 * $LicenseInfo:firstyear=2000&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010-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$
 */

#include "llviewerprecompiledheaders.h"

#include "llviewerregion.h"

// linden libraries
#include "indra_constants.h"
#include "llaisapi.h"
#include "llavatarnamecache.h"		// name lookup cap url
#include "llfloaterreg.h"
#include "llmath.h"
#include "llregionflags.h"
#include "llregionhandle.h"
#include "llsurface.h"
#include "message.h"
//#include "vmath.h"
#include "v3math.h"

#include "llagent.h"
#include "llagentcamera.h"
#include "llappviewer.h"
#include "llavataractions.h"
#include "llavatarrenderinfoaccountant.h"
#include "llcallingcard.h"
#include "llcommandhandler.h"
#include "lleventpoll.h"
#include "llfloatergodtools.h"
#include "llfloaterregioninfo.h"
#include "llhttpnode.h"
#include "lllogininstance.h"
#include "llnotificationsutil.h"
#include "llregioninfomodel.h"
#include "llslurl.h"
#include "llstartup.h"
#include "lltrans.h"
#include "llurldispatcher.h"
#include "llviewerobjectlist.h"
#include "llviewerparcelmgr.h"
#include "llviewerparceloverlay.h"
#include "llviewerstatsrecorder.h"
#include "llvlmanager.h"
#include "llvlcomposition.h"
#include "llvoavatarself.h"
#include "llvocache.h"
#include "llworld.h"
#include "llspatialpartition.h"
#include "llviewercontrol.h"
#include "llsdserialize.h"
#include "llvieweroctree.h"
#include "llviewernetwork.h"
#include "llviewerwindow.h"
#include "llprogressview.h"
#include "llcoros.h"
#include "llcorehttputil.h"
#include "llcallstack.h"

#include "absl/container/node_hash_map.h"
#include <boost/lexical_cast.hpp>
#include "llweb.h"
#include "llcurrencywrapper.h"

// Constants
static const std::string CAP_NAME_SEED("Seed");
static const std::string CAP_NAME_OBJECT_MEDIA("ObjectMedia");

// When we receive a base grant of capabilities that has a different number of 
// capabilities than the original base grant received for the region, print 
// out the two lists of capabilities for analysis.
//#define DEBUG_CAPS_GRANTS

// The server only keeps our pending agent info for 60 seconds.
// We want to allow for seed cap retry, but its not useful after that 60 seconds.
// Give it 3 chances, each at 18 seconds to give ourselves a few seconds to connect anyways if we give up.
const S32 MAX_SEED_CAP_ATTEMPTS_BEFORE_LOGIN = 3;
// Even though we gave up on login, keep trying for caps after we are logged in:
const S32 MAX_CAP_REQUEST_ATTEMPTS = 30;
const U32 DEFAULT_MAX_REGION_WIDE_PRIM_COUNT = 15000;

BOOL LLViewerRegion::sVOCacheCullingEnabled = FALSE;
S32  LLViewerRegion::sLastCameraUpdated = 0;
S32  LLViewerRegion::sNewObjectCreationThrottle = -1;

typedef absl::node_hash_map<std::string, std::string> CapabilityMap;

static void log_capabilities(const CapabilityMap &capmap);

namespace
{

void newRegionEntry(LLViewerRegion& region)
{
    LL_INFOS("LLViewerRegion") << "Entering region [" << region.getName() << "]" << LL_ENDL;
    gDebugInfo["CurrentRegion"] = region.getName();
    LLAppViewer::instance()->writeDebugInfo();
}

} // anonymous namespace

// support for secondlife:///app/region/{REGION} SLapps
// N.B. this is defined to work exactly like the classic secondlife://{REGION}
// However, the later syntax cannot support spaces in the region name because
// spaces (and %20 chars) are illegal in the hostname of an http URL. Some
// browsers let you get away with this, but some do not (such as Qt's Webkit).
// Hence we introduced the newer secondlife:///app/region alternative.
class LLRegionHandler final : public LLCommandHandler
{
public:
    // requests will be throttled from a non-trusted browser
    LLRegionHandler() : LLCommandHandler("region", UNTRUSTED_THROTTLE) {}
       
    bool handle(const LLSD& params, const LLSD& query_map, LLMediaCtrl* web) override
    {
        // make sure that we at least have a region name
        int num_params = params.size();
        if (num_params < 1)
        {
            return false;
        }
           
        // build a secondlife://{PLACE} SLurl from this SLapp
        std::string url = "secondlife://";
        for (int i = 0; i < num_params; i++)
        {
            if (i > 0)
            {
                url += "/";
            }
            url += params[i].asString();
        }
           
        // Process the SLapp as if it was a secondlife://{PLACE} SLurl
        LLURLDispatcher::dispatch(url, "clicked", web, true);
        return true;
    }
       
};
LLRegionHandler gRegionHandler;


class LLViewerRegionImpl 
{
public:
	LLViewerRegionImpl(LLViewerRegion * region, LLHost const & host):   
        mLandp(nullptr),
        mHost(host),
        mCompositionp(nullptr),
        mVOCachePartition(nullptr),
        mEventPoll(nullptr),
        mSeedCapMaxAttempts(MAX_CAP_REQUEST_ATTEMPTS),
        mSeedCapMaxAttemptsBeforeLogin(MAX_SEED_CAP_ATTEMPTS_BEFORE_LOGIN),
        mSeedCapAttempts(0),
        mHttpResponderID(0),
        mLastCameraOrigin(),
        mLastCameraUpdate(0)
	{}

	void buildCapabilityNames(LLSD& capabilityNames);

	// The surfaces and other layers
	LLSurface*	mLandp;

	// Region geometry data
	LLVector3d	mOriginGlobal;	// Location of southwest corner of region (meters)
	LLVector3d	mCenterGlobal;	// Location of center in world space (meters)
	LLHost		mHost;

	// The unique ID for this region.
	LLUUID mRegionID;

	// region/estate owner - usually null.
	LLUUID mOwnerID;

	// Network statistics for the region's circuit...
	LLTimer mLastNetUpdate;

	// Misc
	LLVLComposition *mCompositionp;		// Composition layer for the surface

	LLVOCacheEntry::vocache_entry_map_t	  mCacheMap; //all cached entries
	LLVOCacheEntry::vocache_entry_set_t   mActiveSet; //all active entries;
	LLVOCacheEntry::vocache_entry_set_t   mWaitingSet; //entries waiting for LLDrawable to be generated.	
	std::set< LLPointer<LLViewerOctreeGroup> >      mVisibleGroups; //visible groupa
	LLVOCachePartition*                   mVOCachePartition;
	LLVOCacheEntry::vocache_entry_set_t   mVisibleEntries; //must-be-created visible entries wait for objects creation.	
	LLVOCacheEntry::vocache_entry_priority_list_t mWaitingList; //transient list storing sorted visible entries waiting for object creation.
	std::set<U32>                          mNonCacheableCreatedList; //list of local ids of all non-cacheable objects

	// time?
	// LRU info?

	// Cache ID is unique per-region, across renames, moving locations,
	// etc.
	LLUUID mCacheID;

	CapabilityMap mCapabilities;
	CapabilityMap mSecondCapabilitiesTracker; 
	
	LLEventPoll* mEventPoll;

	S32 mSeedCapMaxAttempts;
	S32 mSeedCapMaxAttemptsBeforeLogin;
	S32 mSeedCapAttempts;

	S32 mHttpResponderID;

	//spatial partitions for objects in this region
	std::vector<LLViewerOctreePartition*> mObjectPartition;

	LLVector3   mLastCameraOrigin;
	U32         mLastCameraUpdate;

    void        requestBaseCapabilitiesCoro(U64 regionHandle);
    void        requestBaseCapabilitiesCompleteCoro(U64 regionHandle);
    void        requestSimulatorFeatureCoro(std::string url, U64 regionHandle);
};

void LLViewerRegionImpl::requestBaseCapabilitiesCoro(U64 regionHandle)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
	auto httpAdapter = std::make_shared<LLCoreHttpUtil::HttpCoroutineAdapter>("BaseCapabilitiesRequest", httpPolicy);
    auto httpRequest = std::make_shared<LLCore::HttpRequest>();

	LLViewerRegion *regionp = nullptr;

    // This loop is used for retrying a capabilities request.
    do
    {
        regionp = LLWorld::getInstance()->getRegionFromHandle(regionHandle);
        if (!regionp) //region was removed
        {
            LL_WARNS("AppInit", "Capabilities") << "Attempting to get capabilities for region that no longer exists!" << LL_ENDL;
            return; // this error condition is not recoverable.
        }
        LL_DEBUGS("AppInit", "Capabilities") << "requesting seed caps for handle " << regionHandle 
                                             << " name " << regionp->getName() << LL_ENDL;

        std::string url = regionp->getCapability(CAP_NAME_SEED);
        if (url.empty())
        {
            LL_WARNS("AppInit", "Capabilities") << "Failed to get seed capabilities, and can not determine url!" << LL_ENDL;
            return; // this error condition is not recoverable.
        }

        // record that we just entered a new region
        newRegionEntry(*regionp);

        // After a few attempts, continue login.  But keep trying to get the caps:
        if (mSeedCapAttempts >= mSeedCapMaxAttemptsBeforeLogin &&
            STATE_SEED_GRANTED_WAIT == LLStartUp::getStartupState())
        {
            LLStartUp::setStartupState(STATE_SEED_CAP_GRANTED);
        }

        if (mSeedCapAttempts > mSeedCapMaxAttempts)
        {
            // *TODO: Give a user pop-up about this error?
            LL_WARNS("AppInit", "Capabilities") << "Failed to get seed capabilities from '" << url << "' after " << mSeedCapAttempts << " attempts.  Giving up!" << LL_ENDL;
            return;  // this error condition is not recoverable.
        }

        S32 id = ++mHttpResponderID;

        LLSD capabilityNames = LLSD::emptyArray();
        buildCapabilityNames(capabilityNames);

        LL_INFOS("AppInit", "Capabilities") << "Requesting seed from " << url 
                                            << " region name " << regionp->getName()
                                            << " (attempt #" << mSeedCapAttempts + 1 << ")" << LL_ENDL;

        regionp = nullptr;
        LLSD result = httpAdapter->postAndSuspend(httpRequest, url, capabilityNames);

        ++mSeedCapAttempts;

        regionp = LLWorld::getInstance()->getRegionFromHandle(regionHandle);
        if (!regionp) //region was removed
        {
            LL_WARNS("AppInit", "Capabilities") << "Received capabilities for region that no longer exists!" << LL_ENDL;
            return; // this error condition is not recoverable.
        }

        if (id != mHttpResponderID) // region is no longer referring to this request
        {
            LL_WARNS("AppInit", "Capabilities") << "Received results for a stale capabilities request!" << LL_ENDL;
            // setup for retry.
            continue;
        }

        if (!result.isMap() || result.has("error"))
        {
            LL_WARNS("AppInit", "Capabilities") << "Malformed response" << LL_ENDL;
            // setup for retry.
            continue;
        }

        LLSD httpResults = result["http_result"];
        LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
        if (!status)
        {
            LL_WARNS("AppInit", "Capabilities") << "HttpStatus error " << LL_ENDL;
            // setup for retry.
            continue;
        }

        // remove the http_result from the llsd
        result.erase("http_result");

	    for (LLSD::map_const_iterator iter = result.beginMap(), end = result.endMap(); iter != end; ++iter)
        {
            regionp->setCapability(iter->first, iter->second);

            LL_DEBUGS("AppInit", "Capabilities")
                << "Capability '" << iter->first << "' is '" << iter->second << "'" << LL_ENDL;
        }

#if 0
        log_capabilities(mCapabilities);
#endif

        regionp->setCapabilitiesReceived(true);
        LL_DEBUGS("AppInit", "Capabilities") << "received caps for handle " << regionHandle 
                                             << " region name " << regionp->getName() << LL_ENDL;

        if (STATE_SEED_GRANTED_WAIT == LLStartUp::getStartupState())
        {
            LLStartUp::setStartupState(STATE_SEED_CAP_GRANTED);
        }

        break;
    } 
    while (true);

    if (regionp && regionp->isCapabilityAvailable("ServerReleaseNotes") &&
            regionp->getReleaseNotesRequested())
    {   // *HACK: we're waiting for the ServerReleaseNotes
        regionp->showReleaseNotes();
    }

}


void LLViewerRegionImpl::requestBaseCapabilitiesCompleteCoro(U64 regionHandle)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
	auto httpAdapter = std::make_shared<LLCoreHttpUtil::HttpCoroutineAdapter>("BaseCapabilitiesRequest", httpPolicy);
    auto httpRequest = std::make_shared<LLCore::HttpRequest>();

	LLViewerRegion *regionp = nullptr;

    // This loop is used for retrying a capabilities request.
    do
    {
        regionp = LLWorld::getInstance()->getRegionFromHandle(regionHandle);
        if (!regionp) //region was removed
        {
            LL_WARNS("AppInit", "Capabilities") << "Attempting to get capabilities for region that no longer exists!" << LL_ENDL;
            break; // this error condition is not recoverable.
        }

        std::string url = regionp->getCapabilityDebug(CAP_NAME_SEED);
        if (url.empty())
        {
            LL_WARNS("AppInit", "Capabilities") << "Failed to get seed capabilities, and can not determine url!" << LL_ENDL;
            break; // this error condition is not recoverable.
        }

        // record that we just entered a new region
        newRegionEntry(*regionp);

        LLSD capabilityNames = LLSD::emptyArray();
        buildCapabilityNames(capabilityNames);

        LL_INFOS("AppInit", "Capabilities") << "Requesting second Seed from " << url << LL_ENDL;

        regionp = nullptr;
        LLSD result = httpAdapter->postAndSuspend(httpRequest, url, capabilityNames);

        LLSD httpResults = result["http_result"];
        LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
        if (!status)
        {
            LL_WARNS("AppInit", "Capabilities") << "HttpStatus error " << LL_ENDL;
            break;  // no retry
        }

        regionp = LLWorld::getInstance()->getRegionFromHandle(regionHandle);
        if (!regionp) //region was removed
        {
            LL_WARNS("AppInit", "Capabilities") << "Received capabilities for region that no longer exists!" << LL_ENDL;
            break; // this error condition is not recoverable.
        }

        // remove the http_result from the llsd
        result.erase("http_result");

	    for (LLSD::map_const_iterator iter = result.beginMap(), end = result.endMap(); iter != end; ++iter)
        {
            regionp->setCapabilityDebug(iter->first, iter->second);
            //LL_INFOS()<<"BaseCapabilitiesCompleteTracker New Caps "<<iter->first<<" "<< iter->second<<LL_ENDL;
        }

#if 0
        log_capabilities(mCapabilities);
#endif

        if (mCapabilities.size() != mSecondCapabilitiesTracker.size())
        {
            LL_WARNS("AppInit", "Capabilities")
                << "Sim sent duplicate base caps that differ in size from what we initially received - most likely content. "
                << "mCapabilities == " << mCapabilities.size()
                << " mSecondCapabilitiesTracker == " << mSecondCapabilitiesTracker.size()
                << LL_ENDL;
#ifdef DEBUG_CAPS_GRANTS
            LL_WARNS("AppInit", "Capabilities")
                << "Initial Base capabilities: " << LL_ENDL;

            log_capabilities(mCapabilities);

            LL_WARNS("AppInit", "Capabilities")
                << "Latest base capabilities: " << LL_ENDL;

            log_capabilities(mSecondCapabilitiesTracker);

#endif

            if (mSecondCapabilitiesTracker.size() > mCapabilities.size())
            {
                // *HACK Since we were granted more base capabilities in this grant request than the initial, replace
                // the old with the new. This shouldn't happen i.e. we should always get the same capabilities from a
                // sim. The simulator fix from SH-3895 should prevent it from happening, at least in the case of the
                // inventory api capability grants.

                // Need to clear a std::map before copying into it because old keys take precedence.
                mCapabilities.clear();
                mCapabilities = mSecondCapabilitiesTracker;
            }
        }
        else
        {
            LL_DEBUGS("CrossingCaps") << "Sim sent multiple base cap grants with matching sizes." << LL_ENDL;
        }
        mSecondCapabilitiesTracker.clear();
    } 
    while (false);


}

void LLViewerRegionImpl::requestSimulatorFeatureCoro(std::string url, U64 regionHandle)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
	auto httpAdapter = std::make_shared<LLCoreHttpUtil::HttpCoroutineAdapter>("BaseCapabilitiesRequest", httpPolicy);
    auto httpRequest = std::make_shared<LLCore::HttpRequest>();

    LLViewerRegion *regionp = nullptr;
    S32 attemptNumber = 0;
    // This loop is used for retrying a capabilities request.
    do
    {
        ++attemptNumber;

        if (attemptNumber > MAX_CAP_REQUEST_ATTEMPTS)
        {
            LL_WARNS("AppInit", "SimulatorFeatures") << "Retries count exceeded attempting to get Simulator feature from " 
                << url << LL_ENDL;
            break;
        }

        regionp = LLWorld::getInstance()->getRegionFromHandle(regionHandle);
        if (!regionp) //region was removed
        {
            LL_WARNS("AppInit", "SimulatorFeatures") << "Attempting to request Sim Feature for region that no longer exists!" << LL_ENDL;
            break; // this error condition is not recoverable.
        }

        regionp = nullptr;
        LLSD result = httpAdapter->getAndSuspend(httpRequest, url);

        LLSD httpResults = result["http_result"];
        LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
        if (!status)
        {
            LL_WARNS("AppInit", "SimulatorFeatures") << "HttpStatus error retrying" << LL_ENDL;
            continue;  
        }

        // remove the http_result from the llsd
        result.erase("http_result");

        regionp = LLWorld::getInstance()->getRegionFromHandle(regionHandle);
        if (!regionp) //region was removed
        {
            LL_WARNS("AppInit", "SimulatorFeatures") << "Attempting to set Sim Feature for region that no longer exists!" << LL_ENDL;
            break; // this error condition is not recoverable.
        }

        regionp->setSimulatorFeatures(result);

        break;
    }
    while (true);

}

LLViewerRegion::LLViewerRegion(const U64 &handle,
							   const LLHost &host,
							   const U32 grids_per_region_edge, 
							   const U32 grids_per_patch_edge, 
							   const F32 region_width_meters)
:	mBitsReceived(0.f),
	mPacketsReceived(0.f),
	mImpl(new LLViewerRegionImpl(this, host)),
	mWidth(region_width_meters),
	mHandle(handle),
	mTimeDilation(1.0f),
	mWidthScaleFactor(region_width_meters / REGION_WIDTH_METERS),
mMaxBakes(LLGridManager::getInstance()->isInSecondlife()?
    LLAvatarAppearanceDefines::EBakedTextureIndex::BAKED_NUM_INDICES:
    LLAvatarAppearanceDefines::EBakedTextureIndex::BAKED_LEFT_ARM),
mMaxTEs(LLGridManager::getInstance()->isInSecondlife()?
    LLAvatarAppearanceDefines::ETextureIndex::TEX_NUM_INDICES:
    LLAvatarAppearanceDefines::ETextureIndex::TEX_HEAD_UNIVERSAL_TATTOO),
	mName(""),
	mZoning(""),
	mIsEstateManager(FALSE),
	mRegionFlags( REGION_FLAGS_DEFAULT ),
	mRegionProtocols( 0 ),
	mSimAccess( SIM_ACCESS_MIN ),
	mBillableFactor(1.0),
	mMaxTasks(DEFAULT_MAX_REGION_WIDE_PRIM_COUNT),
	mCentralBakeVersion(0),
	mLastVisitedEntry(nullptr),
	mInvisibilityCheckHistory(static_cast<U32>(-1)),
	mClassID(0),
	mCPURatio(0),
	mColoName("unknown"),
	mProductSKU("unknown"),
	mProductName("unknown"),
	mLegacyHttpUrl(""),
	mViewerAssetUrl(""),
	mCacheLoaded(FALSE),
	mCacheDirty(FALSE),
	mCapabilitiesReceived(false),
	mSimulatorFeaturesReceived(false),
	mReleaseNotesRequested(FALSE),
	mDead(FALSE),
	mPaused(FALSE),
	mRegionCacheHitCount(0),
	mRegionCacheMissCount(0)
{
	mImpl->mOriginGlobal = from_region_handle(handle); 
	updateRenderMatrix();

	mImpl->mLandp = new LLSurface('l', nullptr);

	// Create the composition layer for the surface
	mImpl->mCompositionp =
		new LLVLComposition(mImpl->mLandp,
							grids_per_region_edge,
							mWidth / grids_per_region_edge);
	mImpl->mCompositionp->setSurface(mImpl->mLandp);

	// Create the surfaces
	mImpl->mLandp->setRegion(this);
	mImpl->mLandp->create(grids_per_region_edge,
					grids_per_patch_edge,
					mImpl->mOriginGlobal,
					mWidth);

	mParcelOverlay = new LLViewerParcelOverlay(this, mWidth);
	LLViewerParcelMgr::getInstance()->init(mWidth);

	setOriginGlobal(from_region_handle(handle));
	calculateCenterGlobal();

	// Create the object lists
	initStats();
	initPartitions();
}

void LLViewerRegion::initPartitions()
{
	//create object partitions
	//MUST MATCH declaration of eObjectPartitions
	mImpl->mObjectPartition.push_back(new LLHUDPartition(this));		//PARTITION_HUD
	mImpl->mObjectPartition.push_back(new LLTerrainPartition(this));	//PARTITION_TERRAIN
	mImpl->mObjectPartition.push_back(new LLVoidWaterPartition(this));	//PARTITION_VOIDWATER
	mImpl->mObjectPartition.push_back(new LLWaterPartition(this));		//PARTITION_WATER
	mImpl->mObjectPartition.push_back(new LLTreePartition(this));		//PARTITION_TREE
	mImpl->mObjectPartition.push_back(new LLParticlePartition(this));	//PARTITION_PARTICLE
	mImpl->mObjectPartition.push_back(new LLGrassPartition(this));		//PARTITION_GRASS
	mImpl->mObjectPartition.push_back(new LLVolumePartition(this));	//PARTITION_VOLUME
	mImpl->mObjectPartition.push_back(new LLBridgePartition(this));	//PARTITION_BRIDGE
	mImpl->mObjectPartition.push_back(new LLHUDParticlePartition(this));//PARTITION_HUD_PARTICLE
	mImpl->mObjectPartition.push_back(new LLVOCachePartition(this)); //PARTITION_VO_CACHE
	mImpl->mObjectPartition.push_back(nullptr);					//PARTITION_NONE
	mImpl->mVOCachePartition = getVOCachePartition();

	setCapabilitiesReceivedCallback(boost::bind(&LLAvatarRenderInfoAccountant::scanNewRegion, _1));
}


void LLViewerRegion::initStats()
{
	mImpl->mLastNetUpdate.reset();
	mPacketsIn = 0;
	mBitsIn = static_cast<U32Bits>(0);
	mLastBitsIn = static_cast<U32Bits>(0);
	mLastPacketsIn = 0;
	mPacketsOut = 0;
	mLastPacketsOut = 0;
	mPacketsLost = 0;
	mLastPacketsLost = 0;
	mPingDelay = static_cast<U32Seconds>(0);
	mAlive = false;					// can become false if circuit disconnects
}

void LLViewerRegion::reInitPartitions()
{
	std::for_each(mImpl->mObjectPartition.begin(), mImpl->mObjectPartition.end(), DeletePointer());
	mImpl->mObjectPartition.clear();
	initPartitions();
}

LLViewerRegion::~LLViewerRegion() 
{
	mDead = TRUE;
	mImpl->mActiveSet.clear();
	mImpl->mVisibleEntries.clear();
	mImpl->mVisibleGroups.clear();
	mImpl->mWaitingSet.clear();

	gVLManager.cleanupData(this);
	// Can't do this on destruction, because the neighbor pointers might be invalid.
	// This should be reference counted...
	disconnectAllNeighbors();
	LLViewerPartSim::getInstance()->cleanupRegion(this);

	gObjectList.killObjects(this);

	delete mImpl->mCompositionp;
	delete mParcelOverlay;
	delete mImpl->mLandp;
	delete mImpl->mEventPoll;
#if 0
	LLHTTPSender::clearSender(mImpl->mHost);
#endif	
	std::for_each(mImpl->mObjectPartition.begin(), mImpl->mObjectPartition.end(), DeletePointer());

	saveObjectCache();

	delete mImpl;
	mImpl = nullptr;

	for (LLPointer<LLViewerTexture> tile : mWorldMapTiles)
		tile->setBoostLevel(LLViewerTexture::BOOST_NONE);
}

/*virtual*/ 
const LLHost&	LLViewerRegion::getHost() const				
{ 
	return mImpl->mHost; 
}

LLSurface & LLViewerRegion::getLand() const
{
	return *mImpl->mLandp;
}

const LLUUID& LLViewerRegion::getRegionID() const
{
	return mImpl->mRegionID;
}

void LLViewerRegion::setRegionID(const LLUUID& region_id)
{
	mImpl->mRegionID = region_id;
}

void LLViewerRegion::loadObjectCache()
{
	if (mCacheLoaded)
	{
		return;
	}

	// Presume success.  If it fails, we don't want to try again.
	mCacheLoaded = TRUE;

	if(LLVOCache::instanceExists())
	{
		LLVOCache::getInstance()->readFromCache(mHandle, mImpl->mCacheID, mImpl->mCacheMap) ;
		if (mImpl->mCacheMap.empty())
		{
			mCacheDirty = TRUE;
		}
	}
}


void LLViewerRegion::saveObjectCache()
{
	if (!mCacheLoaded)
	{
		return;
	}

	if (mImpl->mCacheMap.empty())
	{
		return;
	}

	if(LLVOCache::instanceExists())
	{
		const F32 start_time_threshold = 600.0f; //seconds
		bool removal_enabled = sVOCacheCullingEnabled && (mRegionTimer.getElapsedTimeF32() > start_time_threshold); //allow to remove invalid objects from object cache file.
		
		LLVOCache::getInstance()->writeToCache(mHandle, mImpl->mCacheID, mImpl->mCacheMap, mCacheDirty, removal_enabled) ;
		mCacheDirty = FALSE;
	}

	mImpl->mCacheMap.clear();
}

void LLViewerRegion::sendMessage()
{
	gMessageSystem->sendMessage(mImpl->mHost);
}

void LLViewerRegion::sendReliableMessage()
{
	gMessageSystem->sendReliable(mImpl->mHost);
}

void LLViewerRegion::setWaterHeight(F32 water_level)
{
	mImpl->mLandp->setWaterHeight(water_level);
}

F32 LLViewerRegion::getWaterHeight() const
{
	return mImpl->mLandp->getWaterHeight();
}

BOOL LLViewerRegion::isVoiceEnabled() const
{
	return getRegionFlag(REGION_FLAGS_ALLOW_VOICE);
}

void LLViewerRegion::setRegionFlags(U64 flags)
{
	mRegionFlags = flags;
}


void LLViewerRegion::setOriginGlobal(const LLVector3d &origin_global) 
{ 
	mImpl->mOriginGlobal = origin_global; 
	updateRenderMatrix();
	mImpl->mLandp->setOriginGlobal(origin_global);
	mWind.setOriginGlobal(origin_global);
	calculateCenterGlobal();
}

void LLViewerRegion::updateRenderMatrix()
{
	mRenderMatrix.setTranslation(getOriginAgent());
}

void LLViewerRegion::setTimeDilation(F32 time_dilation)
{
	mTimeDilation = time_dilation;
}

const LLVector3d & LLViewerRegion::getOriginGlobal() const
{
	return mImpl->mOriginGlobal;
}

LLVector3 LLViewerRegion::getOriginAgent() const
{
	return gAgent.getPosAgentFromGlobal(mImpl->mOriginGlobal);
}

const LLVector3d & LLViewerRegion::getCenterGlobal() const
{
	return mImpl->mCenterGlobal;
}

LLVector3 LLViewerRegion::getCenterAgent() const
{
	return gAgent.getPosAgentFromGlobal(mImpl->mCenterGlobal);
}

void LLViewerRegion::setOwner(const LLUUID& owner_id)
{
	mImpl->mOwnerID = owner_id;
}

const LLUUID& LLViewerRegion::getOwner() const
{
	return mImpl->mOwnerID;
}

void LLViewerRegion::setRegionNameAndZone	(const std::string& name_zone)
{
	std::string::size_type pipe_pos = name_zone.find('|');
	S32 length   = name_zone.size();
	if (pipe_pos != std::string::npos)
	{
		mName   = name_zone.substr(0, pipe_pos);
		mZoning = name_zone.substr(pipe_pos+1, length-(pipe_pos+1));
	}
	else
	{
		mName   = name_zone;
		mZoning.clear();
	}

	LLStringUtil::stripNonprintable(mName);
	LLStringUtil::stripNonprintable(mZoning);
}

BOOL LLViewerRegion::canManageEstate() const
{
	return gAgent.isGodlike()
		|| isEstateManager()
		|| gAgent.getID() == getOwner();
}

const std::string& LLViewerRegion::getSimAccessString() const
{
	return accessToString(mSimAccess);
}

std::string LLViewerRegion::getLocalizedSimProductName() const
{
	std::string localized_spn;
	return LLTrans::findString(localized_spn, mProductName) ? localized_spn : mProductName;
}

// static
std::string LLViewerRegion::regionFlagsToString(U64 flags)
{
	std::string result;

	if (flags & REGION_FLAGS_SANDBOX)
	{
		result += "Sandbox";
	}

	if (flags & REGION_FLAGS_ALLOW_DAMAGE)
	{
		result += " Not Safe";
	}

	return result;
}

// static
const std::string& LLViewerRegion::accessToString(U8 sim_access)
{
	switch (sim_access)
	{
	case SIM_ACCESS_PG:
	{
		static std::string sim_access_pg = LLTrans::getString("SIM_ACCESS_PG");
		return sim_access_pg;
	}

	case SIM_ACCESS_MATURE:
	{
		static std::string sim_access_mature = LLTrans::getString("SIM_ACCESS_MATURE");
		return sim_access_mature;
	}

	case SIM_ACCESS_ADULT:
	{
		static std::string sim_access_adult = LLTrans::getString("SIM_ACCESS_ADULT");
		return sim_access_adult;
	}
	case SIM_ACCESS_DOWN:
	{
		static std::string sim_access_down = LLTrans::getString("SIM_ACCESS_DOWN");
		return sim_access_down;
	}
	case SIM_ACCESS_MIN:
	default:
	{
		static std::string sim_access_up = LLTrans::getString("SIM_ACCESS_MIN");
		return sim_access_up;
	}
	}
}

// static
const std::string& LLViewerRegion::getAccessIcon(U8 sim_access)
{
	static const std::string parcel_m_dark("Parcel_M_Dark");
	static const std::string parcel_r_light("Parcel_R_Light");
	static const std::string parcel_pg_light("Parcel_PG_Light");

	switch(sim_access)
	{
	case SIM_ACCESS_MATURE:
		return parcel_m_dark;

	case SIM_ACCESS_ADULT:
		return parcel_r_light;

	case SIM_ACCESS_PG:
		return parcel_pg_light;

	case SIM_ACCESS_MIN:
	default:
		return LLStringUtil::null;
	}
}

// static
const std::string& LLViewerRegion::accessToShortString(U8 sim_access)
{
	static const std::string ACCESS_PG_SHORT_STR("PG");
	static const std::string ACCESS_MATURE_SHORT_STR("M");
	static const std::string ACCESS_ADULT_SHORT_STR("A");
	static const std::string ACCESS_MIN_SHORT_STR("U");

	switch(sim_access)		/* Flawfinder: ignore */
	{
	case SIM_ACCESS_PG:
		return ACCESS_PG_SHORT_STR;

	case SIM_ACCESS_MATURE:
		return ACCESS_MATURE_SHORT_STR;

	case SIM_ACCESS_ADULT:
		return ACCESS_ADULT_SHORT_STR;

	case SIM_ACCESS_MIN:
	default:
		return ACCESS_MIN_SHORT_STR;
	}
}

// static
U8 LLViewerRegion::shortStringToAccess(const std::string &sim_access)
{
	U8 accessValue;

	if (LLStringUtil::compareStrings(sim_access, "PG") == 0)
	{
		accessValue = SIM_ACCESS_PG;
	}
	else if (LLStringUtil::compareStrings(sim_access, "M") == 0)
	{
		accessValue = SIM_ACCESS_MATURE;
	}
	else if (LLStringUtil::compareStrings(sim_access, "A") == 0)
	{
		accessValue = SIM_ACCESS_ADULT;
	}
	else
	{
		accessValue = SIM_ACCESS_MIN;
	}

	return accessValue;
}

// static
void LLViewerRegion::processRegionInfo(LLMessageSystem* msg, void**)
{
	// send it to 'observers'
	// *TODO: switch the floaters to using LLRegionInfoModel
	LL_INFOS() << "Processing region info" << LL_ENDL;
	LLRegionInfoModel::instance().update(msg);
	LLFloaterGodTools::processRegionInfo(msg);
	LLFloaterRegionInfo::processRegionInfo(msg);
}

void LLViewerRegion::setCacheID(const LLUUID& id)
{
	mImpl->mCacheID = id;
}

S32 LLViewerRegion::renderPropertyLines() const
{
	return mParcelOverlay ? mParcelOverlay->renderPropertyLines() : 0;
}

// This gets called when the height field changes.
void LLViewerRegion::dirtyHeights()
{
	// Property lines need to be reconstructed when the land changes.
	if (mParcelOverlay)
	{
		mParcelOverlay->setDirty();
	}
}

//physically delete the cache entry
void LLViewerRegion::killCacheEntry(LLVOCacheEntry* entry, bool for_rendering)
{	
	if(!entry || !entry->isValid())
	{
		return;
	}

	if(for_rendering && !entry->isState(LLVOCacheEntry::ACTIVE))
	{
		addNewObject(entry); //force to add to rendering pipeline
	}

	//remove from active list and waiting list
	if(entry->isState(LLVOCacheEntry::ACTIVE))
	{
		mImpl->mActiveSet.erase(entry);
	}
	else
	{
		if(entry->isState(LLVOCacheEntry::WAITING))
		{
			mImpl->mWaitingSet.erase(entry);
		}
		
		//remove from mVOCachePartition
		removeFromVOCacheTree(entry);
	}

	//remove from the forced visible list
	mImpl->mVisibleEntries.erase(entry);

	//disconnect from parent if it is a child
	if(entry->getParentID() > 0)
	{
		LLVOCacheEntry* parent = getCacheEntry(entry->getParentID());
		if(parent)
		{
			parent->removeChild(entry);
		}
	}
	else if(entry->getNumOfChildren() > 0)//remove children from cache if has any
	{
		LLVOCacheEntry* child = entry->getChild();
		while(child != nullptr)
		{
			killCacheEntry(child, for_rendering);
			child = entry->getChild();
		}
	}

	//will remove it from the object cache, real deletion
	entry->setState(LLVOCacheEntry::INACTIVE);
	entry->removeOctreeEntry();
	entry->setValid(FALSE);
}

//physically delete the cache entry	
void LLViewerRegion::killCacheEntry(U32 local_id) 
{
	killCacheEntry(getCacheEntry(local_id));
}

U32 LLViewerRegion::getNumOfActiveCachedObjects() const
{
	return  mImpl->mActiveSet.size();
}

void LLViewerRegion::addActiveCacheEntry(LLVOCacheEntry* entry)
{
	if(!entry || mDead)
	{
		return;
	}
	if(entry->isState(LLVOCacheEntry::ACTIVE))
	{
		return; //already inserted.
	}

	if(entry->isState(LLVOCacheEntry::WAITING))
	{
		mImpl->mWaitingSet.erase(entry);
	}

	entry->setState(LLVOCacheEntry::ACTIVE);
	entry->setVisible();

	llassert(entry->getEntry()->hasDrawable());
	mImpl->mActiveSet.insert(entry);
}

void LLViewerRegion::removeActiveCacheEntry(LLVOCacheEntry* entry, LLDrawable* drawablep)
{
	if(mDead || !entry || !entry->isValid())
	{
		return;
	}
	if(!entry->isState(LLVOCacheEntry::ACTIVE))
	{
		return; //not an active entry.
	}

	//shift to the local regional space from agent space
	if(drawablep != nullptr && drawablep->getVObj().notNull())
	{
		const LLVector3& pos = drawablep->getVObj()->getPositionRegion();
		LLVector4a shift;
		shift.load3(pos.mV);
		shift.sub(entry->getPositionGroup());
		entry->shift(shift);
	}

	if(entry->getParentID() > 0) //is a child
	{
		LLVOCacheEntry* parent = getCacheEntry(entry->getParentID());
		if(parent)
		{
			parent->addChild(entry);
		}
		else //parent not in cache.
		{
			//this happens only when parent is not cacheable.
			mOrphanMap[entry->getParentID()].push_back(entry->getLocalID());
		}
	}
	else //insert to vo cache tree.
	{		
		entry->updateParentBoundingInfo();
		entry->saveBoundingSphere();
		addToVOCacheTree(entry);
	}

	mImpl->mVisibleEntries.erase(entry);
	mImpl->mActiveSet.erase(entry);
	mImpl->mWaitingSet.erase(entry);
	entry->setState(LLVOCacheEntry::INACTIVE);
}

bool LLViewerRegion::addVisibleGroup(LLViewerOctreeGroup* group)
{
	if(mDead || group->isEmpty())
	{
		return false;
	}
	
	mImpl->mVisibleGroups.insert(group);

	return true;
}

U32 LLViewerRegion::getNumOfVisibleGroups() const
{
	return mImpl ? mImpl->mVisibleGroups.size() : 0;
}

void LLViewerRegion::addToVOCacheTree(LLVOCacheEntry* entry)
{
	if(!sVOCacheCullingEnabled)
	{
		return;
	}

	if(mDead || !entry || !entry->getEntry() || !entry->isValid())
	{
		return;
	}
	if(entry->getParentID() > 0)
	{
		return; //no child prim in cache octree.
	}

	if(entry->hasState(LLVOCacheEntry::IN_VO_TREE))
	{
		return; //already in the tree.
	}	

	llassert_always(!entry->getGroup()); //not in octree.
	llassert(!entry->getEntry()->hasDrawable()); //not have drawables

	if(mImpl->mVOCachePartition->addEntry(entry->getEntry()))
	{
		entry->setState(LLVOCacheEntry::IN_VO_TREE);
	}
}

void LLViewerRegion::removeFromVOCacheTree(LLVOCacheEntry* entry)
{
	if(mDead || !entry || !entry->getEntry())
	{
		return;
	}
	
	if(!entry->hasState(LLVOCacheEntry::IN_VO_TREE))
	{
		return; //not in the tree.
	}
	entry->clearState(LLVOCacheEntry::IN_VO_TREE);

	mImpl->mVOCachePartition->removeEntry(entry->getEntry());	
}

//add child objects as visible entries
void LLViewerRegion::addVisibleChildCacheEntry(LLVOCacheEntry* parent, LLVOCacheEntry* child)
{
	if(mDead)
	{
		return;
	}

	if(parent && (!parent->isValid() || !parent->isState(LLVOCacheEntry::ACTIVE)))
	{
		return; //parent must be valid and in rendering pipeline
	}

	if(child && (!child->getEntry() || !child->isValid() || !child->isState(LLVOCacheEntry::INACTIVE)))
	{
		return; //child must be valid and not in the rendering pipeline
	}

	if(child)
	{
		child->setState(LLVOCacheEntry::IN_QUEUE);
		mImpl->mVisibleEntries.insert(child);
	}	
	else if(parent && parent->getNumOfChildren() > 0) //add all children
	{
		child = parent->getChild();
		while(child != nullptr)
		{
			addVisibleChildCacheEntry(nullptr, child);
			child = parent->getChild();
		}
	}
}

void LLViewerRegion::updateVisibleEntries(F32 max_time)
{
	if(mDead)
	{
		return;
	}

	if(mImpl->mVisibleGroups.empty() && mImpl->mVisibleEntries.empty())
	{
		return;
	}

	if(!sNewObjectCreationThrottle)
	{
		return;
	}

	const F32 LARGE_SCENE_CONTRIBUTION = 1000.f; //a large number to force to load the object.
	const LLVector3 camera_origin = LLViewerCamera::getInstance()->getOrigin();
	const U32 cur_frame = LLViewerOctreeEntryData::getCurrentFrame();
	bool needs_update = ((cur_frame - mImpl->mLastCameraUpdate) > 5) && ((camera_origin - mImpl->mLastCameraOrigin).lengthSquared() > 10.f);	
	U32 last_update = mImpl->mLastCameraUpdate;
	LLVector4a local_origin;
	local_origin.load3((camera_origin - getOriginAgent()).mV);

	//process visible entries
	for(LLVOCacheEntry::vocache_entry_set_t::iterator iter = mImpl->mVisibleEntries.begin(); iter != mImpl->mVisibleEntries.end();)
	{
		LLVOCacheEntry* vo_entry = *iter;
		
		if(vo_entry->isValid() && vo_entry->getState() < LLVOCacheEntry::WAITING)
		{
			//set a large number to force to load this object.
			vo_entry->setSceneContribution(LARGE_SCENE_CONTRIBUTION);
			
			mImpl->mWaitingList.insert(vo_entry);
			++iter;
		}
		else
		{
			LLVOCacheEntry::vocache_entry_set_t::iterator next_iter = iter;
			++next_iter;
			mImpl->mVisibleEntries.erase(iter);
			iter = next_iter;
		}
	}

	//
	//process visible groups
	//
	//object projected area threshold
	F32 projection_threshold = LLVOCacheEntry::getSquaredPixelThreshold(mImpl->mVOCachePartition->isFrontCull());
	F32 dist_threshold = mImpl->mVOCachePartition->isFrontCull() ? gAgentCamera.mDrawDistance : LLVOCacheEntry::sRearFarRadius;
	
	std::set< LLPointer<LLViewerOctreeGroup> >::iterator group_iter = mImpl->mVisibleGroups.begin();
	for(; group_iter != mImpl->mVisibleGroups.end(); ++group_iter)
	{
		LLPointer<LLViewerOctreeGroup> group = *group_iter;
		if(group->getNumRefs() < 3 || //group to be deleted
			!group->getOctreeNode() || group->isEmpty()) //group empty
        {
			continue;
		}

		for (LLViewerOctreeGroup::element_iter i = group->getDataBegin(); i != group->getDataEnd(); ++i)
		{
			if((*i)->hasVOCacheEntry())
			{
				LLVOCacheEntry* vo_entry = (LLVOCacheEntry*)(*i)->getVOCacheEntry();

				if(vo_entry->getParentID() > 0) //is a child
				{
					//child visibility depends on its parent.
					continue;
				}
				if(!vo_entry->isValid())
				{
					continue; //skip invalid entry.
				}

				vo_entry->calcSceneContribution(local_origin, needs_update, last_update, dist_threshold);
				if(vo_entry->getSceneContribution() > projection_threshold)
				{
					mImpl->mWaitingList.insert(vo_entry);			
				}
			}
		}
	}

	if(needs_update)
	{
		mImpl->mLastCameraOrigin = camera_origin;
		mImpl->mLastCameraUpdate = cur_frame;
	}

	return;
}

void LLViewerRegion::createVisibleObjects(F32 max_time)
{
	if(mDead)
	{
		return;
	}
	if(mImpl->mWaitingList.empty())
	{
		mImpl->mVOCachePartition->setCullHistory(FALSE);
		return;
	}	
	
	S32 throttle = sNewObjectCreationThrottle;
	BOOL has_new_obj = FALSE;
	LLTimer update_timer;	
	for (auto vo_entry : mImpl->mWaitingList)
    {
        if(vo_entry->getState() < LLVOCacheEntry::WAITING)
		{
			addNewObject(vo_entry);
			has_new_obj = TRUE;
			if(throttle > 0 && !(--throttle) && update_timer.getElapsedTimeF32() > max_time)
			{
				break;
			}
		}
	}	

	mImpl->mVOCachePartition->setCullHistory(has_new_obj);

	return;
}

void LLViewerRegion::clearCachedVisibleObjects()
{
	mImpl->mWaitingList.clear();
	mImpl->mVisibleGroups.clear();

	//reset all occluders
	mImpl->mVOCachePartition->resetOccluders();
	mPaused = TRUE;

	//clean visible entries
	for(LLVOCacheEntry::vocache_entry_set_t::iterator iter = mImpl->mVisibleEntries.begin(); iter != mImpl->mVisibleEntries.end();)
	{
		LLVOCacheEntry* entry = *iter;
		LLVOCacheEntry* parent = getCacheEntry(entry->getParentID());

		if(!entry->getParentID() || parent) //no child or parent is cache-able
		{
			if(parent) //has a cache-able parent
			{
				parent->addChild(entry);
			}

			LLVOCacheEntry::vocache_entry_set_t::iterator next_iter = iter;
			++next_iter;
			mImpl->mVisibleEntries.erase(iter);
			iter = next_iter;
		}
		else //parent is not cache-able, leave it.
		{
			++iter;
		}
	}

	//remove all visible entries.
	mLastVisitedEntry = nullptr;
	std::vector<LLDrawable*> delete_list;
	for (auto iter : mImpl->mActiveSet)
    {
		LLDrawable* drawablep = (LLDrawable*)iter->getEntry()->getDrawable();
	
		if(drawablep && !drawablep->getParent())
		{
			delete_list.push_back(drawablep);
		}
	}

	if(!delete_list.empty())
	{
		for (auto& i : delete_list)
        {
			gObjectList.killObject(i->getVObj());
		}
		delete_list.clear();
	}

	return;
}

//perform some necessary but very light updates.
//to replace the function idleUpdate(...) in case there is no enough time.
void LLViewerRegion::lightIdleUpdate()
{
	if(!sVOCacheCullingEnabled)
	{
		return;
	}
	if(mImpl->mCacheMap.empty())
	{
		return;
	}

	//reset all occluders
	mImpl->mVOCachePartition->resetOccluders();	
}

void LLViewerRegion::idleUpdate(F32 max_update_time)
{	
	LLTimer update_timer;

	mLastUpdate = LLViewerOctreeEntryData::getCurrentFrame();

	mImpl->mLandp->idleUpdate(max_update_time);
	
	if (mParcelOverlay)
	{
		// Hopefully not a significant time sink...
		mParcelOverlay->idleUpdate();
	}

	if(!sVOCacheCullingEnabled)
	{
		return;
	}
	if(mImpl->mCacheMap.empty())
	{
		return;
	}	
	if(mPaused)
	{
		mPaused = FALSE; //unpause.
	}

	LLViewerCamera::eCameraID old_camera_id = LLViewerCamera::sCurCameraID;
	LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD;

	//reset all occluders
	mImpl->mVOCachePartition->resetOccluders();	

	F32 max_time = max_update_time - update_timer.getElapsedTimeF32();	

	//kill invisible objects
	killInvisibleObjects(max_time * 0.4f);	
	max_time = max_update_time - update_timer.getElapsedTimeF32();	

	updateVisibleEntries(max_time);
	max_time = max_update_time - update_timer.getElapsedTimeF32();	

	createVisibleObjects(max_time);

	mImpl->mWaitingList.clear();
	mImpl->mVisibleGroups.clear();

	LLViewerCamera::sCurCameraID = old_camera_id;
	return;
}

//update the throttling number for new object creation
void LLViewerRegion::calcNewObjectCreationThrottle()
{
	static LLCachedControl<S32> new_object_creation_throttle(gSavedSettings,"NewObjectCreationThrottle");
	static LLCachedControl<F32> throttle_delay_time(gSavedSettings,"NewObjectCreationThrottleDelayTime");
	static LLFrameTimer timer;

	//
	//sNewObjectCreationThrottle =
	//-2: throttle is disabled because either the screen is showing progress view, or immediate after the screen is not black
	//-1: throttle is disabled by the debug setting
	//0:  no new object creation is allowed
	//>0: valid throttling number
	//

	if(gViewerWindow->getProgressView()->getVisible() && throttle_delay_time > 0.f)
	{
		sNewObjectCreationThrottle = -2; //cancel the throttling
		timer.reset();
	}	
	else if(sNewObjectCreationThrottle < -1) //just recoved from the login/teleport screen
	{
		if(timer.getElapsedTimeF32() > throttle_delay_time) //wait for throttle_delay_time to reset the throttle
		{
			sNewObjectCreationThrottle = new_object_creation_throttle; //reset
			if(sNewObjectCreationThrottle < -1)
			{
				sNewObjectCreationThrottle = -1;
			}
		}
	}

	//update some LLVOCacheEntry debug setting factors.
	LLVOCacheEntry::updateDebugSettings();
}

BOOL LLViewerRegion::isViewerCameraStatic()
{
	return sLastCameraUpdated < (S32)LLViewerOctreeEntryData::getCurrentFrame();
}

void LLViewerRegion::killInvisibleObjects(F32 max_time)
{
	if(!sVOCacheCullingEnabled)
	{
		return;
	}
	if(mImpl->mActiveSet.empty())
	{
		return;
	}
	if(sNewObjectCreationThrottle < 0)
	{
		return;
	}

	LLTimer update_timer;
	LLVector4a camera_origin;
	camera_origin.load3(LLViewerCamera::getInstance()->getOrigin().mV);
	LLVector4a local_origin;
	local_origin.load3((LLViewerCamera::getInstance()->getOrigin() - getOriginAgent()).mV);
	F32 back_threshold = LLVOCacheEntry::sRearFarRadius;
	
	size_t max_update = 64; 
	if(!mInvisibilityCheckHistory && isViewerCameraStatic())
	{
		//history is clean, reduce number of checking
		max_update /= 2;
	}
	
	std::vector<LLDrawable*> delete_list;
	S32 update_counter = llmin(max_update, mImpl->mActiveSet.size());
	LLVOCacheEntry::vocache_entry_set_t::iterator iter = mImpl->mActiveSet.upper_bound(mLastVisitedEntry);		

	for(; update_counter > 0; --update_counter, ++iter)
	{	
		if(iter == mImpl->mActiveSet.end())
		{
			iter = mImpl->mActiveSet.begin();
		}
		if((*iter)->getParentID() > 0)
		{
			continue; //skip child objects, they are removed with their parent.
		}

		LLVOCacheEntry* vo_entry = *iter;
		if(!vo_entry->isAnyVisible(camera_origin, local_origin, back_threshold) && vo_entry->mLastCameraUpdated < sLastCameraUpdated)
		{
			killObject(vo_entry, delete_list);
		}

		if(max_time < update_timer.getElapsedTimeF32()) //time out
		{
			break;
		}
	}

	if(iter == mImpl->mActiveSet.end())
	{
		mLastVisitedEntry = nullptr;
	}
	else
	{
		mLastVisitedEntry = *iter;
	}

	mInvisibilityCheckHistory <<= 1;
	if(!delete_list.empty())
	{
		mInvisibilityCheckHistory |= 1;
		S32 count = delete_list.size();
		for(S32 i = 0; i < count; i++)
		{
			gObjectList.killObject(delete_list[i]->getVObj());
		}
		delete_list.clear();
	}

	return;
}

void LLViewerRegion::killObject(LLVOCacheEntry* entry, std::vector<LLDrawable*>& delete_list)
{
	//kill the object.
	LLDrawable* drawablep = (LLDrawable*)entry->getEntry()->getDrawable();
	llassert(drawablep);
	llassert(drawablep->getRegion() == this);

	if(drawablep && !drawablep->getParent())
	{
		LLViewerObject* v_obj = drawablep->getVObj();
		if (v_obj->isSelected()
			|| (v_obj->flagAnimSource() && isAgentAvatarValid() && gAgentAvatarp->hasMotionFromSource(v_obj->getID())))
		{
			// do not remove objects user is interacting with
			((LLViewerOctreeEntryData*)drawablep)->setVisible();
			return;
		}
		LLViewerObject::const_child_list_t& child_list = v_obj->getChildren();
		for (LLViewerObject* child : child_list)
		{
			if(child->mDrawable)
			{
				if( !child->mDrawable->getEntry()
					|| !child->mDrawable->getEntry()->hasVOCacheEntry()
					|| child->isSelected()
					|| (child->flagAnimSource() && isAgentAvatarValid() && gAgentAvatarp->hasMotionFromSource(child->getID())))
				{
					//do not remove parent if any of its children non-cacheable, animating or selected
					//especially for the case that an avatar sits on a cache-able object
					((LLViewerOctreeEntryData*)drawablep)->setVisible();
					return;
				}

				LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*)child->mDrawable->getGroup();
				if(group && group->isAnyRecentlyVisible())
				{
					//set the parent visible if any of its children visible.
					((LLViewerOctreeEntryData*)drawablep)->setVisible();
					return;
				}
			}
		}
		delete_list.push_back(drawablep);				
	}				
}

LLViewerObject* LLViewerRegion::addNewObject(LLVOCacheEntry* entry)
{
	if(!entry || !entry->getEntry())
	{
		if(entry)
		{
			mImpl->mVisibleEntries.erase(entry);
			entry->setState(LLVOCacheEntry::INACTIVE);
		}
		return nullptr;
	}

	LLViewerObject* obj = nullptr;
	if(!entry->getEntry()->hasDrawable()) //not added to the rendering pipeline yet
	{
		//add the object
		obj = gObjectList.processObjectUpdateFromCache(entry, this);
		if(obj)
		{
			if(!entry->isState(LLVOCacheEntry::ACTIVE))
			{
				mImpl->mWaitingSet.insert(entry);
				entry->setState(LLVOCacheEntry::WAITING);
			}
		}
	}
	else
	{
		LLViewerRegion* old_regionp = ((LLDrawable*)entry->getEntry()->getDrawable())->getRegion();
		if(old_regionp != this)
		{
			//this object exists in two regions at the same time;
			//this case can be safely ignored here because
			//server should soon send update message to remove one region for this object.

			LL_WARNS() << "Entry: " << entry->getLocalID() << " exists in two regions at the same time." << LL_ENDL;
			return nullptr;
		}
		
		LL_WARNS() << "Entry: " << entry->getLocalID() << " in rendering pipeline but not set to be active." << LL_ENDL;

		//should not hit here any more, but does not hurt either, just put it back to active list
		addActiveCacheEntry(entry);
	}
	return obj;
}

//update object cache if the object receives a full-update or terse update
//update_type == EObjectUpdateType::OUT_TERSE_IMPROVED or EObjectUpdateType::OUT_FULL
LLViewerObject* LLViewerRegion::updateCacheEntry(U32 local_id, LLViewerObject* objectp, U32 update_type)
{
	if(objectp && update_type != static_cast<U32>(OUT_TERSE_IMPROVED))
	{
		return objectp; //no need to access cache
	}

	LLVOCacheEntry* entry = getCacheEntry(local_id);
	if (!entry)
	{
		return objectp; //not in the cache, do nothing.
	}
	if(!objectp) //object not created
	{
		//create a new object from cache.
		objectp = addNewObject(entry);
	}

	//remove from cache if terse update
	if(update_type == static_cast<U32>(OUT_TERSE_IMPROVED))
	{
		killCacheEntry(entry, true);
	}

	return objectp;
}

// As above, but forcibly do the update.
void LLViewerRegion::forceUpdate()
{
	mImpl->mLandp->idleUpdate(0.f);

	if (mParcelOverlay)
	{
		mParcelOverlay->idleUpdate(true);
	}
}

void LLViewerRegion::connectNeighbor(LLViewerRegion *neighborp, U32 direction)
{
	mImpl->mLandp->connectNeighbor(neighborp->mImpl->mLandp, direction);
}


void LLViewerRegion::disconnectAllNeighbors()
{
	mImpl->mLandp->disconnectAllNeighbors();
}

LLVLComposition * LLViewerRegion::getComposition() const
{
	return mImpl->mCompositionp;
}

F32 LLViewerRegion::getCompositionXY(const S32 x, const S32 y) const
{
	if (x >= mWidth)
	{
		if (y >= mWidth)
		{
			LLVector3d center = getCenterGlobal() + LLVector3d(mWidth, mWidth, 0.f);
			LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromPosGlobal(center);
			if (regionp)
			{
				// OK, we need to do some hackery here - different simulators no longer use
				// the same composition values, necessarily.
				// If we're attempting to blend, then we want to make the fractional part of
				// this region match the fractional of the adjacent.  For now, just minimize
				// the delta.
				F32 our_comp = getComposition()->getValueScaled(mWidth-1.f, mWidth-1.f);
				F32 adj_comp = regionp->getComposition()->getValueScaled(x - regionp->getWidth(), y - regionp->getWidth());
				while (llabs(our_comp - adj_comp) >= 1.f)
				{
					if (our_comp > adj_comp)
					{
						adj_comp += 1.f;
					}
					else
					{
						adj_comp -= 1.f;
					}
				}
				return adj_comp;
			}
		}
		else
		{
			LLVector3d center = getCenterGlobal() + LLVector3d(mWidth, 0.f, 0.f);
			LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromPosGlobal(center);
			if (regionp)
			{
				// OK, we need to do some hackery here - different simulators no longer use
				// the same composition values, necessarily.
				// If we're attempting to blend, then we want to make the fractional part of
				// this region match the fractional of the adjacent.  For now, just minimize
				// the delta.
				F32 our_comp = getComposition()->getValueScaled(mWidth-1.f, (F32)y);
				F32 adj_comp = regionp->getComposition()->getValueScaled(x - regionp->getWidth(), (F32)y);
				while (llabs(our_comp - adj_comp) >= 1.f)
				{
					if (our_comp > adj_comp)
					{
						adj_comp += 1.f;
					}
					else
					{
						adj_comp -= 1.f;
					}
				}
				return adj_comp;
			}
		}
	}
	else if (y >= mWidth)
	{
		LLVector3d center = getCenterGlobal() + LLVector3d(0.f, mWidth, 0.f);
		LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromPosGlobal(center);
		if (regionp)
		{
			// OK, we need to do some hackery here - different simulators no longer use
			// the same composition values, necessarily.
			// If we're attempting to blend, then we want to make the fractional part of
			// this region match the fractional of the adjacent.  For now, just minimize
			// the delta.
			F32 our_comp = getComposition()->getValueScaled((F32)x, mWidth-1.f);
			F32 adj_comp = regionp->getComposition()->getValueScaled((F32)x, y - regionp->getWidth());
			while (llabs(our_comp - adj_comp) >= 1.f)
			{
				if (our_comp > adj_comp)
				{
					adj_comp += 1.f;
				}
				else
				{
					adj_comp -= 1.f;
				}
			}
			return adj_comp;
		}
	}

	return getComposition()->getValueScaled((F32)x, (F32)y);
}

void LLViewerRegion::calculateCenterGlobal() 
{
	mImpl->mCenterGlobal = mImpl->mOriginGlobal;
	mImpl->mCenterGlobal.mdV[VX] += 0.5 * mWidth;
	mImpl->mCenterGlobal.mdV[VY] += 0.5 * mWidth;
	mImpl->mCenterGlobal.mdV[VZ] = 0.5 * mImpl->mLandp->getMinZ() + mImpl->mLandp->getMaxZ();
}

void LLViewerRegion::calculateCameraDistance()
{
	mCameraDistanceSquared = (F32)(gAgentCamera.getCameraPositionGlobal() - getCenterGlobal()).magVecSquared();
}

std::ostream& operator<<(std::ostream &s, const LLViewerRegion &region)
{
	s << "{ ";
	s << region.mImpl->mHost;
	s << " mOriginGlobal = " << region.getOriginGlobal()<< "\n";
    std::string name(region.getName()), zone(region.getZoning());
    if (! name.empty())
    {
        s << " mName         = " << name << '\n';
    }
    if (! zone.empty())
    {
        s << " mZoning       = " << zone << '\n';
    }
	s << "}";
	return s;
}


// ---------------- Protected Member Functions ----------------

void LLViewerRegion::updateNetStats()
{
	F32 dt = mImpl->mLastNetUpdate.getElapsedTimeAndResetF32();

	LLCircuitData *cdp = gMessageSystem->mCircuitInfo.findCircuit(mImpl->mHost);
	if (!cdp)
	{
		mAlive = false;
		return;
	}

	mAlive = true;
	mDeltaTime = dt;

	mLastPacketsIn =	mPacketsIn;
	mLastBitsIn =		mBitsIn;
	mLastPacketsOut =	mPacketsOut;
	mLastPacketsLost =	mPacketsLost;

	mPacketsIn =				cdp->getPacketsIn();
	mBitsIn =					U32Bits(cdp->getBytesIn());
	mPacketsOut =				cdp->getPacketsOut();
	mPacketsLost =				cdp->getPacketsLost();
	mPingDelay =				cdp->getPingDelay();

	mBitsReceived += mBitsIn - mLastBitsIn;
	mPacketsReceived += mPacketsIn - mLastPacketsIn;
}


U32 LLViewerRegion::getPacketsLost() const
{
	LLCircuitData *cdp = gMessageSystem->mCircuitInfo.findCircuit(mImpl->mHost);
	if (!cdp)
	{
		LL_INFOS() << "LLViewerRegion::getPacketsLost couldn't find circuit for " << mImpl->mHost << LL_ENDL;
		return 0;
	}
	else
	{
		return cdp->getPacketsLost();
	}
}

S32 LLViewerRegion::getHttpResponderID() const
{
	return mImpl->mHttpResponderID;
}

BOOL LLViewerRegion::pointInRegionGlobal(const LLVector3d &point_global) const
{
	LLVector3 pos_region = getPosRegionFromGlobal(point_global);

	if (pos_region.mV[VX] < 0)
	{
		return FALSE;
	}
	if (pos_region.mV[VX] >= mWidth)
	{
		return FALSE;
	}
	if (pos_region.mV[VY] < 0)
	{
		return FALSE;
	}
	if (pos_region.mV[VY] >= mWidth)
	{
		return FALSE;
	}
	return TRUE;
}

LLVector3 LLViewerRegion::getPosRegionFromGlobal(const LLVector3d &point_global) const
{
	LLVector3 pos_region;
	pos_region.setVec(point_global - mImpl->mOriginGlobal);
	return pos_region;
}

LLVector3d LLViewerRegion::getPosGlobalFromRegion(const LLVector3 &pos_region) const
{
	LLVector3d pos_region_d;
	pos_region_d.setVec(pos_region);
	return pos_region_d + mImpl->mOriginGlobal;
}

LLVector3 LLViewerRegion::getPosAgentFromRegion(const LLVector3 &pos_region) const
{
	LLVector3d pos_global = getPosGlobalFromRegion(pos_region);

	return gAgent.getPosAgentFromGlobal(pos_global);
}

LLVector3 LLViewerRegion::getPosRegionFromAgent(const LLVector3 &pos_agent) const
{
	return pos_agent - getOriginAgent();
}

F32 LLViewerRegion::getLandHeightRegion(const LLVector3& region_pos)
{
	return mImpl->mLandp->resolveHeightRegion( region_pos );
}

bool LLViewerRegion::isAlive()
{
	return mAlive;
}

BOOL LLViewerRegion::isOwnedSelf(const LLVector3& pos)
{
	if (mParcelOverlay)
	{
		return mParcelOverlay->isOwnedSelf(pos);
	} else {
		return FALSE;
	}
}

// Owned by a group you belong to?  (officer or member)
BOOL LLViewerRegion::isOwnedGroup(const LLVector3& pos)
{
	if (mParcelOverlay)
	{
		return mParcelOverlay->isOwnedGroup(pos);
	} else {
		return FALSE;
	}
}

// the new TCP coarse location handler node
class CoarseLocationUpdate final : public LLHTTPNode
{
public:
	void post(
		ResponsePtr responder,
		const LLSD& context,
		const LLSD& input) const override
	{
		LLHost host(input["sender"].asString());
		LLViewerRegion* region = LLWorld::getInstance()->getRegion(host);
		if( !region )
		{
			return;
		}

		S32 target_index = input["body"]["Index"][0]["Prey"].asInteger();
		S32 you_index    = input["body"]["Index"][0]["You" ].asInteger();

		std::vector<U32>* avatar_locs = &region->mMapAvatars;
		std::vector<LLUUID>* avatar_ids = &region->mMapAvatarIDs;
		avatar_locs->clear();
		avatar_ids->clear();

		//LL_INFOS() << "coarse locations agent[0] " << input["body"]["AgentData"][0]["AgentID"].asUUID() << LL_ENDL;
		//LL_INFOS() << "my agent id = " << gAgent.getID() << LL_ENDL;
		//LL_INFOS() << ll_pretty_print_sd(input) << LL_ENDL;

		LLSD 
			locs   = input["body"]["Location"],
			agents = input["body"]["AgentData"];
		LLSD::array_iterator 
			locs_it = locs.beginArray(), 
			locs_end = locs.endArray(),
			agents_it = agents.beginArray();
		BOOL has_agent_data = input["body"].has("AgentData");

		for (int i=0; locs_it != locs_end; i++, ++locs_it)
		{
			U8 
				x = locs_it->get("X").asInteger(),
				y = locs_it->get("Y").asInteger(),
				z = locs_it->get("Z").asInteger();
			// treat the target specially for the map, and don't add you or the target
			if(i == target_index)
			{
				LLVector3d global_pos(region->getOriginGlobal());
				global_pos.mdV[VX] += (F64)x * region->getWidthScaleFactor();
				global_pos.mdV[VY] += (F64)y * region->getWidthScaleFactor();
				global_pos.mdV[VZ] += (F64)z * 4.0;
				LLAvatarTracker::instance().setTrackedCoarseLocation(global_pos);
			}
			else if( i != you_index)
			{
				U32 pos = 0x0;
				pos |= x;
				pos <<= 8;
				pos |= y;
				pos <<= 8;
				pos |= z;
				avatar_locs->push_back(pos);
				//LL_INFOS() << "next pos: " << x << "," << y << "," << z << ": " << pos << LL_ENDL;
				if(has_agent_data) // for backwards compatibility with old message format
				{
					LLUUID agent_id(agents_it->get("AgentID").asUUID());
					//LL_INFOS() << "next agent: " << agent_id.asString() << LL_ENDL;
					avatar_ids->push_back(agent_id);
				}
			}
			if (has_agent_data)
			{
				++agents_it;
			}
		}
	}
};

// build the coarse location HTTP node under the "/message" URL
LLHTTPRegistration<CoarseLocationUpdate>
   gHTTPRegistrationCoarseLocationUpdate(
	   "/message/CoarseLocationUpdate");


void sendRadarAlert(const LLUUID& agent, const std::string& region_str, bool entering)
{
	// If we're teleporting, we don't want to see the radar's alerts about EVERY agent leaving.
	if(gAgent.getTeleportState() != LLAgent::TELEPORT_NONE && !entering)
	{
		return;
	}
	LLSD args;
	args["AGENT"] = LLSLURL("agent", agent, "inspect").getSLURLString();
	args["REGION"] = region_str;
	static LLCachedControl<bool> sLogToChat(gSavedSettings, "AlchemyRadarAlertsToChat", false);
	if (entering)
	{
		LLNotificationsUtil::add(sLogToChat ? "RadarAlertEnterChat" : "RadarAlertEnter", args,
			LLSD().with("respond_on_mousedown", TRUE),
			boost::bind(&LLAvatarActions::zoomIn, agent));
	}
	else
	{
		LLNotificationsUtil::add(sLogToChat ? "RadarAlertLeaveChat" : "RadarAlertLeave", args);
	}
}

static uuid_vec_t mVecAgents;

// the deprecated coarse location handler
void LLViewerRegion::updateCoarseLocations(LLMessageSystem* msg)
{
	//LL_INFOS() << "CoarseLocationUpdate" << LL_ENDL;
	mMapAvatars.clear();
	mMapAvatarIDs.clear(); // only matters in a rare case but it's good to be safe.
	
	static LLCachedControl<bool> sRadarAlerts(gSavedSettings, "AlchemyRadarAlerts", false);
	LLViewerRegion* cur_region = gAgent.getRegion();
	uuid_vec_t region_agents;

	U8 x_pos = 0;
	U8 y_pos = 0;
	U8 z_pos = 0;

	U32 pos = 0x0;

	S16 agent_index;
	S16 target_index;
	msg->getS16Fast(_PREHASH_Index, _PREHASH_You, agent_index);
	msg->getS16Fast(_PREHASH_Index, _PREHASH_Prey, target_index);

	BOOL has_agent_data = msg->has(_PREHASH_AgentData);
	S32 count = msg->getNumberOfBlocksFast(_PREHASH_Location);
	for(S32 i = 0; i < count; i++)
	{
		msg->getU8Fast(_PREHASH_Location, _PREHASH_X, x_pos, i);
		msg->getU8Fast(_PREHASH_Location, _PREHASH_Y, y_pos, i);
		msg->getU8Fast(_PREHASH_Location, _PREHASH_Z, z_pos, i);
		LLUUID agent_id = LLUUID::null;
		if(has_agent_data)
		{
			msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id, i);
		}

		//LL_INFOS() << "  object X: " << (S32)x_pos << " Y: " << (S32)y_pos
		//		<< " Z: " << (S32)(z_pos * 4)
		//		<< LL_ENDL;

		// treat the target specially for the map
		if(i == target_index)
		{
			LLVector3d global_pos(mImpl->mOriginGlobal);
			global_pos.mdV[VX] += (F64)(x_pos) * mWidthScaleFactor;
			global_pos.mdV[VY] += (F64)(y_pos) * mWidthScaleFactor;
			global_pos.mdV[VZ] += (F64)(z_pos) * 4.0;
			LLAvatarTracker::instance().setTrackedCoarseLocation(global_pos);
		}
		
		//don't add you
		if( i != agent_index)
		{
			pos = 0x0;
			pos |= x_pos;
			pos <<= 8;
			pos |= y_pos;
			pos <<= 8;
			pos |= z_pos;
			mMapAvatars.push_back(pos);
			if(has_agent_data)
			{
				mMapAvatarIDs.push_back(agent_id);
				
				if (this == cur_region)
				{
					region_agents.push_back(agent_id);
					uuid_vec_t::iterator end = mVecAgents.end();
					if (find(mVecAgents.begin(), end, agent_id) == end)
					{
						if (sRadarAlerts)
							sendRadarAlert(agent_id, this->getName(), true);
					}
				}
			}
		}
	}
	if (this == cur_region)
	{
		for (const LLUUID& agent: mVecAgents)
		{
			uuid_vec_t::iterator end = region_agents.end();
			if (find(region_agents.begin(), end, agent) == end)
			{
				if (sRadarAlerts)
					sendRadarAlert(agent, this->getName(), false);
			}
		}
		mVecAgents.clear();
		mVecAgents = region_agents;
	}
}

void LLViewerRegion::getInfo(LLSD& info)
{
	info["Region"]["Host"] = getHost().getIPandPort();
	info["Region"]["Name"] = getName();
	U32 x, y;
	from_region_handle(getHandle(), &x, &y);
	info["Region"]["Handle"]["x"] = (LLSD::Integer)x;
	info["Region"]["Handle"]["y"] = (LLSD::Integer)y;
}

void LLViewerRegion::requestSimulatorFeatures()
{
    LL_DEBUGS("SimulatorFeatures") << "region " << getName() << " ptr " << this
                                   << " trying to request SimulatorFeatures" << LL_ENDL;
    // kick off a request for simulator features
    std::string url = getCapability("SimulatorFeatures");
    if (!url.empty())
    {
        std::string coroname =
            LLCoros::instance().launch("LLViewerRegionImpl::requestSimulatorFeatureCoro",
                                       boost::bind(&LLViewerRegionImpl::requestSimulatorFeatureCoro, mImpl, url, getHandle()));
        
        LL_INFOS("AppInit", "SimulatorFeatures") << "Launching " << coroname << " requesting simulator features from " << url << LL_ENDL;
    }
    else
    {
        LL_WARNS("AppInit", "SimulatorFeatures") << "SimulatorFeatures cap not set" << LL_ENDL;
    }
}

boost::signals2::connection LLViewerRegion::setSimulatorFeaturesReceivedCallback(const caps_received_signal_t::slot_type& cb)
{
	return mSimulatorFeaturesReceivedSignal.connect(cb);
}

void LLViewerRegion::setSimulatorFeaturesReceived(bool received)
{
	mSimulatorFeaturesReceived = received;
	if (received)
	{
		mSimulatorFeaturesReceivedSignal(getRegionID());
		mSimulatorFeaturesReceivedSignal.disconnect_all_slots();
	}
}

bool LLViewerRegion::simulatorFeaturesReceived() const
{
	return mSimulatorFeaturesReceived;
}

void LLViewerRegion::getSimulatorFeatures(LLSD& sim_features) const
{
	sim_features = mSimulatorFeatures;

}

void LLViewerRegion::setSimulatorFeatures(const LLSD& sim_features)
{
	std::stringstream str;
	
	LLSDSerialize::toPrettyXML(sim_features, str);
	LL_INFOS() << "region " << getName() << " "  << str.str() << LL_ENDL;
	mSimulatorFeatures = sim_features;
	if (LLGridManager::getInstance()->isInOpenSim())
	{
		setGodnames();
        std::string cur_symbol = LLCurrencyWrapper::instance().getHomeCurrency();
		if (mSimulatorFeatures.has("OpenSimExtras"))
		{
			if (mSimulatorFeatures["OpenSimExtras"].has("GridURL"))
			{
				const std::string& grid_url = mSimulatorFeatures["OpenSimExtras"]["GridURL"].asString();
				if (LLGridManager::getInstance()->getGrid(LLURI(grid_url).authority()).empty())
					LLGridManager::getInstance()->addRemoteGrid(grid_url, LLGridManager::ADD_HYPERGRID);
			}
			if (mSimulatorFeatures["OpenSimExtras"].has("currency"))
			{
				cur_symbol = mSimulatorFeatures["OpenSimExtras"]["currency"].asString();
			}
		}
        if (LLCurrencyWrapper::instance().getCurrency() != cur_symbol)
        {
            LLCurrencyWrapper::instance().setCurrency(cur_symbol);
        }
	}
	
	if (mSimulatorFeatures.has("BakesOnMeshEnabled") && (mSimulatorFeatures["BakesOnMeshEnabled"].asBoolean()==true))
	{
		mMaxBakes = LLAvatarAppearanceDefines::EBakedTextureIndex::BAKED_NUM_INDICES;
		mMaxTEs   = LLAvatarAppearanceDefines::ETextureIndex::TEX_NUM_INDICES;
	}
	else
	{
		mMaxBakes = LLAvatarAppearanceDefines::EBakedTextureIndex::BAKED_LEFT_ARM;
		mMaxTEs   = LLAvatarAppearanceDefines::ETextureIndex::TEX_HEAD_UNIVERSAL_TATTOO;
	}
	setSimulatorFeaturesReceived(true);
}

//this is called when the parent is not cacheable.
//move all orphan children out of cache and insert to rendering octree.
void LLViewerRegion::findOrphans(U32 parent_id)
{
	orphan_list_t::const_iterator iter = mOrphanMap.find(parent_id);
	if(iter != mOrphanMap.cend())
	{
		std::vector<U32>* children = &mOrphanMap[parent_id];
		for (unsigned int i : *children)
        {
			//parent is visible, so is the child.
			addVisibleChildCacheEntry(nullptr, getCacheEntry(i));
		}
		children->clear();
		mOrphanMap.erase(parent_id);
	}
}

void LLViewerRegion::decodeBoundingInfo(LLVOCacheEntry* entry)
{
	if(!sVOCacheCullingEnabled)
	{
		gObjectList.processObjectUpdateFromCache(entry, this);
		return;
	}
	if(!entry || !entry->isValid())
	{
		return;
	}

	if(!entry->getEntry())
	{
		entry->setOctreeEntry(nullptr);
	}
		
	if(entry->getEntry()->hasDrawable()) //already in the rendering pipeline
	{
		LLViewerRegion* old_regionp = ((LLDrawable*)entry->getEntry()->getDrawable())->getRegion();
		if(old_regionp != this && old_regionp)
        {
			LLViewerObject* obj = ((LLDrawable*)entry->getEntry()->getDrawable())->getVObj();
			if(obj)
			{
				//remove from old region
				old_regionp->killCacheEntry(obj->getLocalID());

				//change region
				obj->setRegion(this);
			}
		}

		addActiveCacheEntry(entry);

		//set parent id
		U32	parent_id = 0;
		LLViewerObject::unpackParentID(entry->getDP(), parent_id);
		if(parent_id != entry->getParentID())
		{				
			entry->setParentID(parent_id);
		}

		//update the object
		gObjectList.processObjectUpdateFromCache(entry, this);
		return; //done
	}
	
	//must not be active.
	llassert_always(!entry->isState(LLVOCacheEntry::ACTIVE));
	removeFromVOCacheTree(entry); //remove from cache octree if it is in.

	LLVector3 pos;
	LLVector3 scale;
	LLQuaternion rot;

	//decode spatial info and parent info
	U32 parent_id = LLViewerObject::extractSpatialExtents(entry->getDP(), pos, scale, rot);
	
	U32 old_parent_id = entry->getParentID();
	bool same_old_parent = false;
	if(parent_id != old_parent_id) //parent changed.
	{
		if(old_parent_id > 0) //has an old parent, disconnect it
		{
			LLVOCacheEntry* old_parent = getCacheEntry(old_parent_id);
			if(old_parent)
			{
				old_parent->removeChild(entry);
				if(!old_parent->isState(LLVOCacheEntry::INACTIVE))
				{
					mImpl->mVisibleEntries.erase(entry);
					entry->setState(LLVOCacheEntry::INACTIVE);
				}
			}
		}
		entry->setParentID(parent_id);
	}
	else
	{
		same_old_parent = true;
	}

	if(parent_id > 0) //has a new parent
	{	
		//1, find the parent in cache
		LLVOCacheEntry* parent = getCacheEntry(parent_id);
		
		//2, parent is not in the cache, put into the orphan list.
		if(!parent)
		{
			if(!same_old_parent)
			{
				//check if parent is non-cacheable and already created
				if(isNonCacheableObjectCreated(parent_id))
				{
					//parent is visible, so is the child.
					addVisibleChildCacheEntry(nullptr, entry);
				}
				else
				{
					entry->setBoundingInfo(pos, scale);
					mOrphanMap[parent_id].push_back(entry->getLocalID());
				}
			}
			else
			{
				entry->setBoundingInfo(pos, scale);
			}
		}
		else //parent in cache.
		{
			if(!parent->isState(LLVOCacheEntry::INACTIVE)) 
			{
				//parent is visible, so is the child.
				addVisibleChildCacheEntry(parent, entry);
			}
			else
			{
				entry->setBoundingInfo(pos, scale);
				parent->addChild(entry);

				if(parent->getGroup()) //re-insert parent to vo-cache tree because its bounding info changed.
				{
					removeFromVOCacheTree(parent);
					addToVOCacheTree(parent);
				}
			}
		}

		return;
	}
	
	//
	//no parent
	//
	entry->setBoundingInfo(pos, scale);	

	if(!parent_id) //a potential parent
	{
		//find all children and update their bounding info
		orphan_list_t::iterator iter = mOrphanMap.find(entry->getLocalID());
		if(iter != mOrphanMap.end())
		{			
			std::vector<U32>* orphans = &mOrphanMap[entry->getLocalID()];
			S32 size = orphans->size();
			for(S32 i = 0; i < size; i++)
			{
				LLVOCacheEntry* child = getCacheEntry((*orphans)[i]);
				if(child)
				{
					entry->addChild(child);
				}
			}
			orphans->clear();
			mOrphanMap.erase(entry->getLocalID());
		}
	}
	
	if(!entry->getGroup() && entry->isState(LLVOCacheEntry::INACTIVE))
	{
		addToVOCacheTree(entry);
	}
	return ;
}

LLViewerRegion::eCacheUpdateResult LLViewerRegion::cacheFullUpdate(LLDataPackerBinaryBuffer &dp, U32 flags)
{
	eCacheUpdateResult result;
	U32 crc;
	U32 local_id;

	LLViewerObject::unpackU32(&dp, local_id, "LocalID");
	LLViewerObject::unpackU32(&dp, crc, "CRC");

	LLVOCacheEntry* entry = getCacheEntry(local_id, false);

	if (entry)
	{
		entry->setValid();

		// we've seen this object before
		if (entry->getCRC() == crc)
		{
            LL_DEBUGS("AnimatedObjects") << " got dupe for local_id " << local_id << LL_ENDL;
            dumpStack("AnimatedObjectsStack");

			// Record a hit
			entry->recordDupe();
			result = CACHE_UPDATE_DUPE;
		}
		else //CRC changed
		{
            LL_DEBUGS("AnimatedObjects") << " got update for local_id " << local_id << LL_ENDL;
            dumpStack("AnimatedObjectsStack");

			// Update the cache entry
			entry->updateEntry(crc, dp);

			decodeBoundingInfo(entry);

			result = CACHE_UPDATE_CHANGED;
		}		
	}
	else
	{
        LL_DEBUGS("AnimatedObjects") << " got first notification for local_id " << local_id << LL_ENDL;
        dumpStack("AnimatedObjectsStack");

		// we haven't seen this object before
		// Create new entry and add to map
		result = CACHE_UPDATE_ADDED;
		entry = new LLVOCacheEntry(local_id, crc, dp);
		record(LLStatViewer::OBJECT_CACHE_HIT_RATE, LLUnits::Ratio::fromValue(0));
		
		mImpl->mCacheMap[local_id] = entry;
		
		decodeBoundingInfo(entry);
	}
	entry->setUpdateFlags(flags);

	return result;
	}

LLViewerRegion::eCacheUpdateResult LLViewerRegion::cacheFullUpdate(LLViewerObject* objectp, LLDataPackerBinaryBuffer &dp, U32 flags)
{
	eCacheUpdateResult result = cacheFullUpdate(dp, flags);

	return result;
}

LLVOCacheEntry* LLViewerRegion::getCacheEntryForOctree(U32 local_id)
{
	if(!sVOCacheCullingEnabled)
	{
		return nullptr;
	}

	LLVOCacheEntry* entry = getCacheEntry(local_id);
	removeFromVOCacheTree(entry);
		
	return entry;
}

LLVOCacheEntry* LLViewerRegion::getCacheEntry(U32 local_id, bool valid)
{
	LLVOCacheEntry::vocache_entry_map_t::iterator iter = mImpl->mCacheMap.find(local_id);
	if(iter != mImpl->mCacheMap.end())
	{
		if(!valid || iter->second->isValid())
		{
			return iter->second;
		}
	}
	return nullptr;
	}

void LLViewerRegion::addCacheMiss(U32 id, LLViewerRegion::eCacheMissType miss_type)
{
	mRegionCacheMissCount++;
#if 0
	mCacheMissList.insert(CacheMissItem(id, miss_type));
#else
	mCacheMissList.push_back(CacheMissItem(id, miss_type));
#endif
}

//check if a non-cacheable object is already created.
bool LLViewerRegion::isNonCacheableObjectCreated(U32 local_id)
{
	if(mImpl && local_id > 0 && mImpl->mNonCacheableCreatedList.find(local_id) != mImpl->mNonCacheableCreatedList.end())
	{
		return true;
	}
	return false;
}
		
void LLViewerRegion::removeFromCreatedList(U32 local_id)
{	
	if(mImpl && local_id > 0)
	{
		std::set<U32>::iterator iter = mImpl->mNonCacheableCreatedList.find(local_id);
		if(iter != mImpl->mNonCacheableCreatedList.end())
		{
			mImpl->mNonCacheableCreatedList.erase(iter);
		}
	}
	}

void LLViewerRegion::addToCreatedList(U32 local_id)
{
	if(mImpl && local_id > 0)
	{
		mImpl->mNonCacheableCreatedList.insert(local_id);
	}
}

// Get data packer for this object, if we have cached data
// AND the CRC matches. JC
bool LLViewerRegion::probeCache(U32 local_id, U32 crc, U32 flags, U8 &cache_miss_type)
{
	//llassert(mCacheLoaded);  This assert failes often, changing to early-out -- davep, 2010/10/18

	LLVOCacheEntry* entry = getCacheEntry(local_id, false);

	if (entry)
	{
		// we've seen this object before
		if (entry->getCRC() == crc)
		{
			// Record a hit
			mRegionCacheHitCount++;
			entry->recordHit();
            cache_miss_type = CACHE_MISS_TYPE_NONE;
			entry->setUpdateFlags(flags);
			
			if(entry->isState(LLVOCacheEntry::ACTIVE))
			{
				((LLDrawable*)entry->getEntry()->getDrawable())->getVObj()->loadFlags(flags);
				return true;
			}

			if(entry->isValid())
			{
				return true; //already probed
			}

			entry->setValid();
			decodeBoundingInfo(entry);
			return true;
		}
		else
		{
			// LL_INFOS() << "CRC miss for " << local_id << LL_ENDL;

			addCacheMiss(local_id, CACHE_MISS_TYPE_CRC);
            cache_miss_type = CACHE_MISS_TYPE_CRC;
		}
	}
	else
	{
		// LL_INFOS() << "Cache miss for " << local_id << LL_ENDL;
		addCacheMiss(local_id, CACHE_MISS_TYPE_FULL);
        cache_miss_type = CACHE_MISS_TYPE_FULL;
	}

	return false;
}

void LLViewerRegion::addCacheMissFull(const U32 local_id)
{
	addCacheMiss(local_id, CACHE_MISS_TYPE_FULL);
}

void LLViewerRegion::requestCacheMisses()
{
	if (mCacheMissList.empty()) 
	{
		return;
	}

	LLMessageSystem* msg = gMessageSystem;
	BOOL start_new_message = TRUE;
	S32 blocks = 0;

	//send requests for all cache-missed objects
	for (auto& iter : mCacheMissList)
    {
		if (start_new_message)
		{
			msg->newMessageFast(_PREHASH_RequestMultipleObjects);
			msg->nextBlockFast(_PREHASH_AgentData);
			msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
			msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
			start_new_message = FALSE;
		}

		msg->nextBlockFast(_PREHASH_ObjectData);
		msg->addU8Fast(_PREHASH_CacheMissType, iter.mType);
		msg->addU32Fast(_PREHASH_ID, iter.mID);

        LL_DEBUGS("AnimatedObjects") << "Requesting cache missed object " << iter.mID << LL_ENDL;
        
		blocks++;

		if (blocks >= 255)
		{
			sendReliableMessage();
			start_new_message = TRUE;
			blocks = 0;
		}
	}

	// finish any pending message
	if (!start_new_message)
	{
		sendReliableMessage();
	}

	mCacheDirty = TRUE ;
	// LL_INFOS() << "KILLDEBUG Sent cache miss full " << full_count << " crc " << crc_count << LL_ENDL;
	LLViewerStatsRecorder::instance().requestCacheMissesEvent(mCacheMissList.size());
	LLViewerStatsRecorder::instance().log(0.2f);

	mCacheMissList.clear();
}

void LLViewerRegion::dumpCache()
{
	const S32 BINS = 4;
	S32 hit_bin[BINS];
	S32 change_bin[BINS];

	S32 i;
	for (i = 0; i < BINS; ++i)
	{
		hit_bin[i] = 0;
		change_bin[i] = 0;
	}

	for (auto& iter : mImpl->mCacheMap)
    {
		LLVOCacheEntry *entry = iter.second;

		S32 hits = entry->getHitCount();
		S32 changes = entry->getCRCChangeCount();

		hits = llclamp(hits, 0, BINS-1);
		changes = llclamp(changes, 0, BINS-1);

		hit_bin[hits]++;
		change_bin[changes]++;
	}

	LL_INFOS() << "Count " << mImpl->mCacheMap.size() << LL_ENDL;
	for (i = 0; i < BINS; i++)
	{
		LL_INFOS() << "Hits " << i << " " << hit_bin[i] << LL_ENDL;
	}
	for (i = 0; i < BINS; i++)
	{
		LL_INFOS() << "Changes " << i << " " << change_bin[i] << LL_ENDL;
	}
}

void LLViewerRegion::unpackRegionHandshake()
{
	LLMessageSystem *msg = gMessageSystem;

	U64 region_flags = 0;
	U64 region_protocols = 0;
	U8 sim_access;
	std::string sim_name;
	LLUUID sim_owner;
	BOOL is_estate_manager;
	F32 water_height;
	F32 billable_factor;
	LLUUID cache_id;

	msg->getU8		("RegionInfo", "SimAccess", sim_access);
	msg->getString	("RegionInfo", "SimName", sim_name);
	msg->getUUID	("RegionInfo", "SimOwner", sim_owner);
	msg->getBOOL	("RegionInfo", "IsEstateManager", is_estate_manager);
	msg->getF32		("RegionInfo", "WaterHeight", water_height);
	msg->getF32		("RegionInfo", "BillableFactor", billable_factor);
	msg->getUUID	("RegionInfo", "CacheID", cache_id );

	if (msg->has(_PREHASH_RegionInfo4))
	{
		msg->getU64Fast(_PREHASH_RegionInfo4, _PREHASH_RegionFlagsExtended, region_flags);
		msg->getU64Fast(_PREHASH_RegionInfo4, _PREHASH_RegionProtocols, region_protocols);
	}
	else
	{
		U32 flags = 0;
		msg->getU32Fast(_PREHASH_RegionInfo, _PREHASH_RegionFlags, flags);
		region_flags = flags;
	}

	setRegionFlags(region_flags);
	setRegionProtocols(region_protocols);
	setSimAccess(sim_access);
	setRegionNameAndZone(sim_name);
	setOwner(sim_owner);
	setIsEstateManager(is_estate_manager);
	setWaterHeight(water_height);
	setBillableFactor(billable_factor);
	setCacheID(cache_id);

	LLUUID region_id;
	msg->getUUID("RegionInfo2", "RegionID", region_id);
	setRegionID(region_id);
	
	// Retrieve the CR-53 (Homestead/Land SKU) information
	S32 classID = 0;
	S32 cpuRatio = 0;
	std::string coloName;
	std::string productSKU;
	std::string productName;

	// the only reasonable way to decide if we actually have any data is to
	// check to see if any of these fields have positive sizes
	if (msg->getSize("RegionInfo3", "ColoName") > 0 ||
	    msg->getSize("RegionInfo3", "ProductSKU") > 0 ||
	    msg->getSize("RegionInfo3", "ProductName") > 0)
	{
		msg->getS32     ("RegionInfo3", "CPUClassID",  classID);
		msg->getS32     ("RegionInfo3", "CPURatio",    cpuRatio);
		msg->getString  ("RegionInfo3", "ColoName",    coloName);
		msg->getString  ("RegionInfo3", "ProductSKU",  productSKU);
		msg->getString  ("RegionInfo3", "ProductName", productName);
		
		mClassID = classID;
		mCPURatio = cpuRatio;
		mColoName = coloName;
		mProductSKU = productSKU;
		mProductName = productName;
	}


	mCentralBakeVersion = region_protocols & 1; // was (S32)gSavedSettings.getBOOL("UseServerTextureBaking");
	LLVLComposition *compp = getComposition();
	if (compp)
	{
		LLUUID tmp_id;

		bool changed = false;

		// Get the 4 textures for land
		msg->getUUID("RegionInfo", "TerrainDetail0", tmp_id);
		changed |= (tmp_id != compp->getDetailTextureID(0));		
		compp->setDetailTextureID(0, tmp_id);

		msg->getUUID("RegionInfo", "TerrainDetail1", tmp_id);
		changed |= (tmp_id != compp->getDetailTextureID(1));		
		compp->setDetailTextureID(1, tmp_id);

		msg->getUUID("RegionInfo", "TerrainDetail2", tmp_id);
		changed |= (tmp_id != compp->getDetailTextureID(2));		
		compp->setDetailTextureID(2, tmp_id);

		msg->getUUID("RegionInfo", "TerrainDetail3", tmp_id);
		changed |= (tmp_id != compp->getDetailTextureID(3));		
		compp->setDetailTextureID(3, tmp_id);

		// Get the start altitude and range values for land textures
		F32 tmp_f32;
		msg->getF32("RegionInfo", "TerrainStartHeight00", tmp_f32);
		changed |= (tmp_f32 != compp->getStartHeight(0));
		compp->setStartHeight(0, tmp_f32);

		msg->getF32("RegionInfo", "TerrainStartHeight01", tmp_f32);
		changed |= (tmp_f32 != compp->getStartHeight(1));
		compp->setStartHeight(1, tmp_f32);

		msg->getF32("RegionInfo", "TerrainStartHeight10", tmp_f32);
		changed |= (tmp_f32 != compp->getStartHeight(2));
		compp->setStartHeight(2, tmp_f32);

		msg->getF32("RegionInfo", "TerrainStartHeight11", tmp_f32);
		changed |= (tmp_f32 != compp->getStartHeight(3));
		compp->setStartHeight(3, tmp_f32);


		msg->getF32("RegionInfo", "TerrainHeightRange00", tmp_f32);
		changed |= (tmp_f32 != compp->getHeightRange(0));
		compp->setHeightRange(0, tmp_f32);

		msg->getF32("RegionInfo", "TerrainHeightRange01", tmp_f32);
		changed |= (tmp_f32 != compp->getHeightRange(1));
		compp->setHeightRange(1, tmp_f32);

		msg->getF32("RegionInfo", "TerrainHeightRange10", tmp_f32);
		changed |= (tmp_f32 != compp->getHeightRange(2));
		compp->setHeightRange(2, tmp_f32);

		msg->getF32("RegionInfo", "TerrainHeightRange11", tmp_f32);
		changed |= (tmp_f32 != compp->getHeightRange(3));
		compp->setHeightRange(3, tmp_f32);

		// If this is an UPDATE (params already ready, we need to regenerate
		// all of our terrain stuff, by
		if (compp->getParamsReady())
		{
			// Update if the land changed
			if (changed)
			{
				getLand().dirtyAllPatches();
			}
		}
		else
		{
			compp->setParamsReady();
		}
	}


	// Now that we have the name, we can load the cache file
	// off disk.
	loadObjectCache();

	// After loading cache, signal that simulator can start
	// sending data.
	// TODO: Send all upstream viewer->sim handshake info here.
	LLHost host = msg->getSender();
	msg->newMessage("RegionHandshakeReply");
	msg->nextBlock("AgentData");
	msg->addUUID("AgentID", gAgent.getID());
	msg->addUUID("SessionID", gAgent.getSessionID());
	msg->nextBlock("RegionInfo");

	U32 flags = 0;
	flags |= REGION_HANDSHAKE_SUPPORTS_SELF_APPEARANCE;

	if(sVOCacheCullingEnabled)
	{
		flags |= 0x00000001; //set the bit 0 to be 1 to ask sim to send all cacheable objects.		
	}
	if(mImpl->mCacheMap.empty())
	{
		flags |= 0x00000002; //set the bit 1 to be 1 to tell sim the cache file is empty, no need to send cache probes.
	}
	msg->addU32("Flags", flags );
	msg->sendReliable(host);

	mRegionTimer.reset(); //reset region timer.
}

void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames)
{
	capabilityNames.append("AbuseCategories");
	capabilityNames.append("AcceptFriendship");
	capabilityNames.append("AcceptGroupInvite"); // ReadOfflineMsgs recieved messages only!!!
	capabilityNames.append("AgentPreferences");
	capabilityNames.append("AgentState");
	capabilityNames.append("AttachmentResources");
	capabilityNames.append("AvatarPickerSearch");
	capabilityNames.append("AvatarRenderInfo");
	capabilityNames.append("CharacterProperties");
	capabilityNames.append("ChatSessionRequest");
	capabilityNames.append("CopyInventoryFromNotecard");
	capabilityNames.append("CreateInventoryCategory");
	capabilityNames.append("DeclineFriendship");
	capabilityNames.append("DeclineGroupInvite"); // ReadOfflineMsgs recieved messages only!!!
	capabilityNames.append("DispatchRegionInfo");
	capabilityNames.append("DirectDelivery");
	capabilityNames.append("EnvironmentSettings");
	capabilityNames.append("EstateAccess");
	capabilityNames.append("EstateChangeInfo");
	capabilityNames.append("EventQueueGet");

	capabilityNames.append("FetchLib2");
	capabilityNames.append("FetchLibDescendents2");
	capabilityNames.append("FetchInventory2");
	capabilityNames.append("FetchInventoryDescendents2");
	capabilityNames.append("IncrementCOFVersion");
	AISAPI::getCapNames(capabilityNames);

	capabilityNames.append("GetDisplayNames");
	capabilityNames.append("GetExperiences");
	capabilityNames.append("AgentExperiences");
	capabilityNames.append("FindExperienceByName");
	capabilityNames.append("GetExperienceInfo");
	capabilityNames.append("GetAdminExperiences");
	capabilityNames.append("GetCreatorExperiences");
	capabilityNames.append("ExperiencePreferences");
	capabilityNames.append("GroupExperiences");
	capabilityNames.append("UpdateExperience");
	capabilityNames.append("IsExperienceAdmin");
	capabilityNames.append("IsExperienceContributor");
	capabilityNames.append("RegionExperiences");
	capabilityNames.append("GetMesh");
	capabilityNames.append("GetMesh2");
	capabilityNames.append("GetMetadata");
	capabilityNames.append("GetObjectCost");
	capabilityNames.append("GetObjectPhysicsData");
	capabilityNames.append("GetTexture");
	capabilityNames.append("GroupAPIv1");
	capabilityNames.append("GroupMemberData");
	capabilityNames.append("GroupProposalBallot");
	capabilityNames.append("HomeLocation");
	capabilityNames.append("LandResources");
	capabilityNames.append("LSLSyntax");
	capabilityNames.append("MapLayer");
	capabilityNames.append("MapLayerGod");
	capabilityNames.append("MeshUploadFlag");
	capabilityNames.append("NavMeshGenerationStatus");
	capabilityNames.append("NewFileAgentInventory");
	capabilityNames.append("ObjectAnimation");
	capabilityNames.append(CAP_NAME_OBJECT_MEDIA);
	capabilityNames.append("ObjectMediaNavigate");
	capabilityNames.append("ObjectNavMeshProperties");
	capabilityNames.append("ParcelPropertiesUpdate");
	capabilityNames.append("ParcelVoiceInfoRequest");
	capabilityNames.append("ProductInfoRequest");
	capabilityNames.append("ProvisionVoiceAccountRequest");
	capabilityNames.append("ReadOfflineMsgs"); // Requires to respond reliably: AcceptFriendship, AcceptGroupInvite, DeclineFriendship, DeclineGroupInvite
	capabilityNames.append("RemoteParcelRequest");
	capabilityNames.append("RenderMaterials");
	capabilityNames.append("RequestTextureDownload");
	capabilityNames.append("ResourceCostSelected");
	capabilityNames.append("RetrieveNavMeshSrc");
	capabilityNames.append("SearchStatRequest");
	capabilityNames.append("SearchStatTracking");
	capabilityNames.append("SendPostcard");
	capabilityNames.append("SendUserReport");
	capabilityNames.append("SendUserReportWithScreenshot");
	capabilityNames.append("ServerReleaseNotes");
	capabilityNames.append("SetDisplayName");
	capabilityNames.append("SimConsoleAsync");
	capabilityNames.append("SimulatorFeatures");
	capabilityNames.append("StartGroupProposal");
	capabilityNames.append("TerrainNavMeshProperties");
	capabilityNames.append("TextureStats");
	capabilityNames.append("UntrustedSimulatorMessage");
	capabilityNames.append("UpdateAgentInformation");
	capabilityNames.append("UpdateAgentLanguage");
	capabilityNames.append("UpdateAvatarAppearance");
	capabilityNames.append("UpdateGestureAgentInventory");
	capabilityNames.append("UpdateGestureTaskInventory");
	capabilityNames.append("UpdateNotecardAgentInventory");
	capabilityNames.append("UpdateNotecardTaskInventory");
	capabilityNames.append("UpdateScriptAgent");
	capabilityNames.append("UpdateScriptTask");
	capabilityNames.append("UploadBakedTexture");
    capabilityNames.append("UserInfo");
	capabilityNames.append("ViewerAsset"); 
	capabilityNames.append("ViewerMetrics");
	capabilityNames.append("ViewerStartAuction");
	capabilityNames.append("ViewerStats");
	
	// Please add new capabilities alphabetically to reduce
	// merge conflicts.
}

void LLViewerRegion::setSeedCapability(const std::string& url)
{
	if (getCapability(CAP_NAME_SEED) == url)
    {	
		setCapabilityDebug(CAP_NAME_SEED, url);
		LL_WARNS("CrossingCaps") <<  "Received duplicate seed capability, posting to seed " <<
				url	<< LL_ENDL;

		//Instead of just returning we build up a second set of seed caps and compare them 
		//to the "original" seed cap received and determine why there is problem!
        LLCoros::instance().launch("LLEnvironmentRequest::requestBaseCapabilitiesCompleteCoro",
            boost::bind(&LLViewerRegionImpl::requestBaseCapabilitiesCompleteCoro, mImpl, getHandle()));
		return;
    }
	
	delete mImpl->mEventPoll;
	mImpl->mEventPoll = nullptr;
	
	mImpl->mCapabilities.clear();
	setCapability(CAP_NAME_SEED, url);

    std::string coroname =
        LLCoros::instance().launch("LLViewerRegionImpl::requestBaseCapabilitiesCoro",
        boost::bind(&LLViewerRegionImpl::requestBaseCapabilitiesCoro, mImpl, getHandle()));

    LL_INFOS("AppInit", "Capabilities") << "Launching " << coroname << " requesting seed capabilities from " << url << LL_ENDL;
}

S32 LLViewerRegion::getNumSeedCapRetries()
{
	return mImpl->mSeedCapAttempts;
}

void LLViewerRegion::setCapability(const std::string& name, const std::string& url)
{
	bool add_to_mapping = true;

	std::string base_url = get_base_cap_url(url);
	//we need a multimap, since CERTAIN PEOPLE use non-unique URIs for each Cap.
	//let's check if this cap name is already registered for this URI
	//TODO: Better represented as map of sets
	if(mCapURLMappings.count(base_url) > 0)
	{
		auto iter = mCapURLMappings.find(base_url);

		while(iter != mCapURLMappings.cend())
		{
			if(iter->second == name)
			{
				add_to_mapping = false;
				break;
			}
			++iter;
		}
	}

	if(add_to_mapping)
		mCapURLMappings.insert(std::pair<std::string, std::string>(base_url, name));

	if(name == "EventQueueGet")
	{
		delete mImpl->mEventPoll;
		mImpl->mEventPoll = nullptr;
		mImpl->mEventPoll = new LLEventPoll(url, getHost());
	}
	else if(name == "UntrustedSimulatorMessage")
	{
        mImpl->mHost.setUntrustedSimulatorCap(url);
	}
	else if (name == "SimulatorFeatures")
	{
        mImpl->mCapabilities["SimulatorFeatures"] = url;
        requestSimulatorFeatures();
	}
	else
	{
		mImpl->mCapabilities[name] = url;
		if(name == "ViewerAsset")
		{
			/*==============================================================*/
			// The following inserted lines are a hack for testing MAINT-7081,
			// which is why the indentation and formatting are left ugly.
			const char* VIEWERASSET = getenv("VIEWERASSET");
			if (VIEWERASSET)
			{
				mImpl->mCapabilities[name] = VIEWERASSET;
				mViewerAssetUrl = VIEWERASSET;
			}
			else
			{
				mViewerAssetUrl = url;
			}
			/*==============================================================*/
		}
		else if (name == "GetTexture")
		{
			mLegacyHttpUrl = url;
		}
	}
}

void LLViewerRegion::setCapabilityDebug(const std::string& name, const std::string& url)
{
	// Continue to not add certain caps, as we do in setCapability. This is so they match up when we check them later.
	if ( ! ( name == "EventQueueGet" || name == "UntrustedSimulatorMessage" || name == "SimulatorFeatures" ) )
	{
		mImpl->mSecondCapabilitiesTracker[name] = url;
		if(name == "ViewerAsset")
		{
			/*==============================================================*/
			// The following inserted lines are a hack for testing MAINT-7081,
			// which is why the indentation and formatting are left ugly.
			const char* VIEWERASSET = getenv("VIEWERASSET");
			if (VIEWERASSET)
			{
				mImpl->mSecondCapabilitiesTracker[name] = VIEWERASSET;
				mViewerAssetUrl = VIEWERASSET;
			}
			else
			{
				mViewerAssetUrl = url;
			}
			/*==============================================================*/
		}
		else if (name == "GetTexture")
		{
			mLegacyHttpUrl = url;
		}
	}
}

std::string LLViewerRegion::getCapabilityDebug(const std::string& name) const
{
    CapabilityMap::const_iterator iter = mImpl->mSecondCapabilitiesTracker.find(name);
    if (iter == mImpl->mSecondCapabilitiesTracker.end())
    {
        return std::string();
    }

    return iter->second;
}


bool LLViewerRegion::isSpecialCapabilityName(const std::string &name)
{
	return name == "EventQueueGet" || name == "UntrustedSimulatorMessage";
}

std::string LLViewerRegion::getCapability(const std::string& name) const
{
	if (!capabilitiesReceived() && (name != CAP_NAME_SEED) && (name != CAP_NAME_OBJECT_MEDIA))
	{
		LL_WARNS() << "getCapability called before caps received for " << name << LL_ENDL;
	}
	
	CapabilityMap::const_iterator iter = mImpl->mCapabilities.find(name);
	if(iter == mImpl->mCapabilities.end())
	{
		return std::string();
	}

	return iter->second;
}

bool LLViewerRegion::isCapabilityAvailable(const std::string& name) const
{
	if (!capabilitiesReceived() && (name!= CAP_NAME_SEED) && (name != CAP_NAME_OBJECT_MEDIA))
	{
		LL_WARNS() << "isCapabilityAvailable called before caps received for " << name << LL_ENDL;
	}
	
	CapabilityMap::const_iterator iter = mImpl->mCapabilities.find(name);
	if(iter == mImpl->mCapabilities.end())
	{
		return false;
	}

	return true;
}

std::set<std::string> LLViewerRegion::getCapURLNames(const std::string &cap_url)
{
	std::set<std::string> url_capnames;
	if(mCapURLMappings.count(cap_url) > 0)
	{
		auto range = mCapURLMappings.equal_range(cap_url);

		for (url_mapping_t::iterator iter = range.first; iter != range.second; ++iter)
		{
			url_capnames.insert(iter->second);
		}
	}
	return url_capnames;
}


bool LLViewerRegion::isCapURLMapped(const std::string &cap_url)
{
	return (mCapURLMappings.count(cap_url) > 0);
}

std::set<std::string> LLViewerRegion::getAllCaps()
{
	std::set<std::string> url_capnames;
	for (auto& capability : mImpl->mCapabilities)
    {
		url_capnames.insert(capability.first);
	}
	return url_capnames;
}

bool LLViewerRegion::capabilitiesReceived() const
{
	return mCapabilitiesReceived;
}

void LLViewerRegion::setCapabilitiesReceived(bool received)
{
	mCapabilitiesReceived = received;

	// Tell interested parties that we've received capabilities,
	// so that they can safely use getCapability().
	if (received)
	{
		mCapabilitiesReceivedSignal(getRegionID());

		// This is a single-shot signal. Forget callbacks to save resources.
		mCapabilitiesReceivedSignal.disconnect_all_slots();
	}
}

boost::signals2::connection LLViewerRegion::setCapabilitiesReceivedCallback(const caps_received_signal_t::slot_type& cb)
{
	return mCapabilitiesReceivedSignal.connect(cb);
}

void LLViewerRegion::logActiveCapabilities() const
{
	log_capabilities(mImpl->mCapabilities);
}

LLSpatialPartition* LLViewerRegion::getSpatialPartition(U32 type)
{
	if (type < mImpl->mObjectPartition.size() && type < PARTITION_VO_CACHE)
	{
		return (LLSpatialPartition*)mImpl->mObjectPartition[type];
	}
	return nullptr;
}

LLVOCachePartition* LLViewerRegion::getVOCachePartition()
{
	if(PARTITION_VO_CACHE < mImpl->mObjectPartition.size())
	{
		return (LLVOCachePartition*)mImpl->mObjectPartition[PARTITION_VO_CACHE];
	}
	return nullptr;
}

// the viewer can not yet distinquish between normal- and estate-owned objects
// so we collapse these two bits and enable the UI if either are set
const U64 ALLOW_RETURN_ENCROACHING_OBJECT = REGION_FLAGS_ALLOW_RETURN_ENCROACHING_OBJECT
											| REGION_FLAGS_ALLOW_RETURN_ENCROACHING_ESTATE_OBJECT;

bool LLViewerRegion::objectIsReturnable(const LLVector3& pos, const std::vector<LLBBox>& boxes) const
{
	return (mParcelOverlay != nullptr)
		&& (mParcelOverlay->isOwnedSelf(pos)
			|| mParcelOverlay->isOwnedGroup(pos)
			|| (getRegionFlag(ALLOW_RETURN_ENCROACHING_OBJECT)
				&& mParcelOverlay->encroachesOwned(boxes)) );
}

bool LLViewerRegion::childrenObjectReturnable( const std::vector<LLBBox>& boxes ) const
{
	bool result = (mParcelOverlay && mParcelOverlay->encroachesOnUnowned(boxes));
	return result;
}

bool LLViewerRegion::objectsCrossParcel(const std::vector<LLBBox>& boxes) const
{
	return mParcelOverlay && mParcelOverlay->encroachesOnNearbyParcel(boxes);
}

void LLViewerRegion::getNeighboringRegions( std::vector<LLViewerRegion*>& uniqueRegions )
{
	mImpl->mLandp->getNeighboringRegions( uniqueRegions );
}
void LLViewerRegion::getNeighboringRegionsStatus( std::vector<S32>& regions )
{
	mImpl->mLandp->getNeighboringRegionsStatus( regions );
}
void LLViewerRegion::showReleaseNotes()
{
	std::string url = this->getCapability("ServerReleaseNotes");

	if (url.empty()) {
		// HACK haven't received the capability yet, we'll wait until
		// it arives.
		mReleaseNotesRequested = TRUE;
		return;
	}

	LLWeb::loadURL(url);
	mReleaseNotesRequested = FALSE;
}

std::string LLViewerRegion::getDescription() const
{
	return boost::lexical_cast<std::string>(*this);
}

bool LLViewerRegion::meshUploadEnabled() const
{
	return (mSimulatorFeatures.has("MeshUploadEnabled") &&
		mSimulatorFeatures["MeshUploadEnabled"].asBoolean());
}

bool LLViewerRegion::bakesOnMeshEnabled() const
{
	return (mSimulatorFeatures.has("BakesOnMeshEnabled") &&
		mSimulatorFeatures["BakesOnMeshEnabled"].asBoolean());
}

bool LLViewerRegion::meshRezEnabled() const
{
	return (mSimulatorFeatures.has("MeshRezEnabled") &&
				mSimulatorFeatures["MeshRezEnabled"].asBoolean());
}

bool LLViewerRegion::dynamicPathfindingEnabled() const
{
	return ( mSimulatorFeatures.has("DynamicPathfindingEnabled") &&
			 mSimulatorFeatures["DynamicPathfindingEnabled"].asBoolean());
}

bool LLViewerRegion::avatarHoverHeightEnabled() const
{
	return ( mSimulatorFeatures.has("AvatarHoverHeightEnabled") &&
			 mSimulatorFeatures["AvatarHoverHeightEnabled"].asBoolean());
}
/* Static Functions */

void log_capabilities(const CapabilityMap &capmap)
{
	S32 count = 0;
	for (CapabilityMap::const_iterator iter = capmap.cbegin(); iter != capmap.cend(); ++iter, ++count)
	{
		if (!iter->second.empty())
		{
			LL_INFOS() << "log_capabilities: " << iter->first << " URL is " << iter->second << LL_ENDL;
		}
	}
	LL_INFOS() << "log_capabilities: Dumped " << count << " entries." << LL_ENDL;
}
void LLViewerRegion::resetMaterialsCapThrottle()
{
	F32 requests_per_sec = 	1.0f; // original default;
	if (   mSimulatorFeatures.has("RenderMaterialsCapability")
		&& mSimulatorFeatures["RenderMaterialsCapability"].isReal() )
	{
		requests_per_sec = mSimulatorFeatures["RenderMaterialsCapability"].asReal();
		if ( requests_per_sec == 0.0f )
		{
			requests_per_sec = 1.0f;
			LL_WARNS("Materials")
				<< "region '" << getName()
				<< "' returned zero for RenderMaterialsCapability; using default "
				<< requests_per_sec << " per second"
				<< LL_ENDL;
		}
		LL_DEBUGS("Materials") << "region '" << getName()
							   << "' RenderMaterialsCapability " << requests_per_sec
							   << LL_ENDL;
	}
	else
	{
		LL_DEBUGS("Materials")
			<< "region '" << getName()
			<< "' did not return RenderMaterialsCapability, using default "
			<< requests_per_sec << " per second"
			<< LL_ENDL;
	}
	
	mMaterialsCapThrottleTimer.resetWithExpiry( 1.0f / requests_per_sec );
}

U32 LLViewerRegion::getMaxMaterialsPerTransaction() const
{
	U32 max_entries = 50; // original hard coded default
	if (   mSimulatorFeatures.has( "MaxMaterialsPerTransaction" )
		&& mSimulatorFeatures[ "MaxMaterialsPerTransaction" ].isInteger())
	{
		max_entries = mSimulatorFeatures[ "MaxMaterialsPerTransaction" ].asInteger();
	}
	return max_entries;
}

// <alchemy>
bool LLViewerRegion::getRegionAllowsExport() const
{
	if (mSimulatorFeatures.has("OpenSimExtras")
		&& mSimulatorFeatures["OpenSimExtras"].has("ExportSupported"))
	{
		return mSimulatorFeatures["OpenSimExtras"]["ExportSupported"].asBoolean();
	}
	return false;
}

std::string LLViewerRegion::getAvatarPickerURL() const
{
	std::string url = LLStringUtil::null;
	if (mSimulatorFeatures.has("OpenSimExtras")
		&& mSimulatorFeatures["OpenSimExtras"].has("avatar-picker-url"))
	{
		url = mSimulatorFeatures["OpenSimExtras"]["avatar-picker-url"].asString();
	}
	else if (LLLoginInstance::getInstance()->hasResponse("avatar_picker_url"))
	{
		url = LLLoginInstance::getInstance()->getResponse("avatar_picker_url").asString();
	}
	else if (LLGridManager::getInstance()->isInSecondlife())
	{
		url = gSavedSettings.getString("AvatarPickerURL");
	}
	return url;
}

std::string LLViewerRegion::getDestinationGuideURL() const
{
	std::string url = LLStringUtil::null;
	if (mSimulatorFeatures.has("OpenSimExtras")
		&& mSimulatorFeatures["OpenSimExtras"].has("destination-guide-url"))
	{
		url = mSimulatorFeatures["OpenSimExtras"]["destination-guide-url"].asString();
	}
	else if (LLLoginInstance::getInstance()->hasResponse("destination_guide_url"))
	{
		url = LLLoginInstance::getInstance()->getResponse("destination_guide_url").asString();
	}
	else if (LLGridManager::getInstance()->isInSecondlife())
	{
		url = gSavedSettings.getString("DestinationGuideURL");
	}
	return url;
}

std::string LLViewerRegion::getMapServerURL() const
{
	std::string url;
	if (mSimulatorFeatures.has("OpenSimExtras")
		&& mSimulatorFeatures["OpenSimExtras"].has("map-server-url"))
	{
		url = mSimulatorFeatures["OpenSimExtras"]["map-server-url"].asString();
	}
	else
	{
		url = gSavedSettings.getString("CurrentMapServerURL");
	}
	return url;
}

std::string LLViewerRegion::getSearchServerURL() const
{
	std::string url;
	// Check the region it trumps the grid
	if (mSimulatorFeatures.has("OpenSimExtras")
		&& mSimulatorFeatures["OpenSimExtras"].has("search-server-url"))
	{
		url = mSimulatorFeatures["OpenSimExtras"]["search-server-url"].asString();
	}
	// Check the login message
	else if (LLLoginInstance::getInstance()->hasResponse("search"))
	{
		url = LLLoginInstance::getInstance()->getResponse("search").asString();
	}
	// If all else fails, fall back to defaults
	else
	{
		url = gSavedSettings.getString(LLGridManager::getInstance()->isInOpenSim() ? "OpenSimSearchURL" : "SearchURL");
	}
	return url;
}

std::string LLViewerRegion::getBuyCurrencyServerURL() const
{
	std::string url = LLGridManager::getInstance()->getHelperURI() + "currency.php";
	// If we have the feature, override grid default.
	if (mSimulatorFeatures.has("OpenSimExtras")
		&& mSimulatorFeatures["OpenSimExtras"].has("currency-base-uri"))
	{
		url = mSimulatorFeatures["OpenSimExtras"]["currency-base-uri"].asString();
	}
	return url;
}

std::string LLViewerRegion::getHGGrid() const
{
	std::string authority = LLStringUtil::null;
	if (mSimulatorFeatures.has("OpenSimExtras")
		&& mSimulatorFeatures["OpenSimExtras"].has("GridURL"))
	{
		const std::string& url = mSimulatorFeatures["OpenSimExtras"]["GridURL"].asString();
		authority = LLURI(url).authority();
	}
	else
	{
		authority = LLGridManager::getInstance()->getGrid();
	}
	return authority;
}

std::string LLViewerRegion::getHGGridName() const
{
	std::string name;
	if (mSimulatorFeatures.has("OpenSimExtras")
		&& mSimulatorFeatures["OpenSimExtras"].has("GridName"))
	{
		name = mSimulatorFeatures["OpenSimExtras"]["GridName"].asString();
	}
	else
	{
		name = LLGridManager::getInstance()->getGridLabel();
	}
	return name;
}

U32 LLViewerRegion::getChatRange() const
{
    U32 range = 20;
    if (mSimulatorFeatures.has("OpenSimExtras")
        && mSimulatorFeatures["OpenSimExtras"].has("say-range"))
    {
        range = mSimulatorFeatures["OpenSimExtras"]["say-range"].asInteger();
    }
    return range;
}

U32 LLViewerRegion::getShoutRange() const
{
    U32 range = 100;
    if (mSimulatorFeatures.has("OpenSimExtras")
        && mSimulatorFeatures["OpenSimExtras"].has("shout-range"))
    {
        range = mSimulatorFeatures["OpenSimExtras"]["shout-range"].asInteger();
    }
    return range;
}

U32 LLViewerRegion::getWhisperRange() const
{
    U32 range = 10;
    if (mSimulatorFeatures.has("OpenSimExtras")
        && mSimulatorFeatures["OpenSimExtras"].has("whisper-range"))
    {
        range = mSimulatorFeatures["OpenSimExtras"]["whisper-range"].asInteger();
    }
    return range;
}

void LLViewerRegion::setGodnames()
{
	mGodNames.clear();
	if (mSimulatorFeatures.has("god_names"))
	{
		if (mSimulatorFeatures["god_names"].has("full_names"))
		{
			LLSD god_names = mSimulatorFeatures["god_names"]["full_names"];
			for (LLSD::array_const_iterator itr = god_names.beginArray(),
				ite = god_names.endArray();
				 itr != ite;
				 ++itr)
			{
				mGodNames.insert((*itr).asString());
			}
		}
		if (mSimulatorFeatures["god_names"].has("last_names"))
		{
			LLSD god_names = mSimulatorFeatures["god_names"]["last_names"];
			for (LLSD::array_const_iterator itr = god_names.beginArray(), ite = god_names.endArray();
				 itr != ite;
				 ++itr)
			{
				mGodNames.insert((*itr).asString());
			}
		}
	}
}

const LLViewerRegion::tex_matrix_t& LLViewerRegion::getWorldMapTiles() const
{
	if (mWorldMapTiles.empty())
	{
		U32 gridX, gridY;
		grid_from_region_handle(mHandle, &gridX, &gridY);
		U32 totalX(getWidth() / REGION_WIDTH_U32);
		if (!totalX) ++totalX; // If this region is too small, still get an image.
		// *TODO: Non-square regions?
		//U32 totalY(getLength()/REGION_WIDTH_U32);
		//if (!totalY) ++totalY; // If this region is too small, still get an image.
		const U32 totalY(totalX);
		mWorldMapTiles.reserve(totalX * totalY);
		for (U32 x = 0; x != totalX; ++x)
			for (U32 y = 0; y != totalY; ++y)
			{
				const std::string map_url = getMapServerURL().append(llformat("map-1-%d-%d-objects.jpg", gridX + x, gridY + y));
				LLPointer<LLViewerTexture> tex(LLViewerTextureManager::getFetchedTextureFromUrl(map_url, FTT_MAP_TILE, TRUE,
											   LLViewerTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE));
				mWorldMapTiles.push_back(tex);
				tex->setBoostLevel(LLViewerTexture::BOOST_MAP);
			}
	}
	return mWorldMapTiles;
}
// </alchemy>

