diff options
25 files changed, 1383 insertions, 85 deletions
| @@ -143,3 +143,4 @@ a9abb9633a266c8d2fe62411cfd1c86d32da72bf 2.7.1-release  19a498fa62570f352d7d246f17e3c81cc1d82d8b 2.7.5-start  09984bfa6cae17e0f72d02b75c1b7393c65eecfc DRTVWR-69_2.7.5-beta1  09984bfa6cae17e0f72d02b75c1b7393c65eecfc 2.7.5-beta1 +e1ed60913230dd64269a7f7fc52cbc6004f6d52c 2.8.0-start diff --git a/doc/contributions.txt b/doc/contributions.txt index bad78dcd5f..68b0a4279f 100644 --- a/doc/contributions.txt +++ b/doc/contributions.txt @@ -20,6 +20,7 @@ Aimee Trescothick  	SNOW-570  	SNOW-572  	SNOW-575 +	STORM-1315  	VWR-3321  	VWR-3336  	VWR-3903 @@ -103,6 +104,7 @@ Ales Beaumont  Alexandrea Fride      STORM-255  	STORM-960 +	STORM-1459  Alissa Sabre  	VWR-81  	VWR-83 @@ -201,6 +203,7 @@ Boroondas Gupte  	OPEN-29  	OPEN-39  	OPEN-39 +	OPEN-99  	SNOW-278  	SNOW-503  	SNOW-510 @@ -449,6 +452,7 @@ Jonathan Yap  	STORM-899  	STORM-1273  	STORM-1462 +	STORM-1459  Kage Pixel  	VWR-11  Ken March @@ -690,6 +694,7 @@ Robin Cornelius  	STORM-1019  	STORM-1095  	STORM-1128 +	STORM-1459  	VWR-2488  	VWR-9557  	VWR-10579 @@ -831,6 +836,7 @@ Thickbrick Sleaford  	VWR-24420  	STORM-956  	STORM-1147 +	STORM-1325  Thraxis Epsilon  	SVC-371  	VWR-383 diff --git a/indra/cmake/00-Common.cmake b/indra/cmake/00-Common.cmake index 2c974fb4ff..0266239454 100644 --- a/indra/cmake/00-Common.cmake +++ b/indra/cmake/00-Common.cmake @@ -171,7 +171,10 @@ if (LINUX)      add_definitions(-fvisibility=hidden)      # don't catch SIGCHLD in our base application class for the viewer - some of our 3rd party libs may need their *own* SIGCHLD handler to work.  Sigh!  The viewer doesn't need to catch SIGCHLD anyway.      add_definitions(-DLL_IGNORE_SIGCHLD) -    add_definitions(-march=pentium4 -mfpmath=sse) +    if (WORD_SIZE EQUAL 32) +      add_definitions(-march=pentium4) +    endif (WORD_SIZE EQUAL 32) +    add_definitions(-mfpmath=sse)      #add_definitions(-ftree-vectorize) # THIS CRASHES GCC 3.1-3.2      if (NOT STANDALONE)        # this stops us requiring a really recent glibc at runtime diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp index e8616a9be6..99e61433c6 100644 --- a/indra/llcommon/llsys.cpp +++ b/indra/llcommon/llsys.cpp @@ -1,6 +1,6 @@  /**    * @file llsys.cpp - * @brief Impelementation of the basic system query functions. + * @brief Implementation of the basic system query functions.   *   * $LicenseInfo:firstyear=2002&license=viewerlgpl$   * Second Life Viewer Source Code @@ -24,6 +24,10 @@   * $/LicenseInfo$   */ +#if LL_WINDOWS +#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally +#endif +  #include "linden_common.h"  #include "llsys.h" @@ -36,22 +40,43 @@  #endif  #include "llprocessor.h" +#include "llerrorcontrol.h" +#include "llevents.h" +#include "lltimer.h" +#include "llsdserialize.h" +#include "llsdutil.h" +#include <boost/bind.hpp> +#include <boost/circular_buffer.hpp> +#include <boost/regex.hpp> +#include <boost/foreach.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/range.hpp> +#include <boost/utility/enable_if.hpp> +#include <boost/type_traits/is_integral.hpp> +#include <boost/type_traits/is_float.hpp> + +using namespace llsd;  #if LL_WINDOWS  #	define WIN32_LEAN_AND_MEAN  #	include <winsock2.h>  #	include <windows.h> +#   include <psapi.h>               // GetPerformanceInfo() et al.  #elif LL_DARWIN  #	include <errno.h>  #	include <sys/sysctl.h>  #	include <sys/utsname.h>  #	include <stdint.h>  #	include <Carbon/Carbon.h> +#   include <sys/wait.h> +#   include <string.h> +#   include <stdexcept>  #elif LL_LINUX  #	include <errno.h>  #	include <sys/utsname.h>  #	include <unistd.h>  #	include <sys/sysinfo.h> +#   include <stdexcept>  const char MEMINFO_FILE[] = "/proc/meminfo";  #elif LL_SOLARIS  #	include <stdio.h> @@ -70,6 +95,15 @@ extern int errno;  static const S32 CPUINFO_BUFFER_SIZE = 16383;  LLCPUInfo gSysCPU; +// Don't log memory info any more often than this. It also serves as our +// framerate sample size. +static const F32 MEM_INFO_THROTTLE = 20; +// Sliding window of samples. We intentionally limit the length of time we +// remember "the slowest" framerate because framerate is very slow at login. +// If we only triggered FrameWatcher logging when the session framerate +// dropped below the login framerate, we'd have very little additional data. +static const F32 MEM_INFO_WINDOW = 10*60; +  #if LL_WINDOWS  #ifndef DLLVERSIONINFO  typedef struct _DllVersionInfo @@ -613,8 +647,78 @@ void LLCPUInfo::stream(std::ostream& s) const  	s << "->mCPUString:  " << mCPUString << std::endl;  } +// Helper class for LLMemoryInfo: accumulate stats in the form we store for +// LLMemoryInfo::getStatsMap(). +class Stats +{ +public: +	Stats(): +		mStats(LLSD::emptyMap()) +	{} + +	// Store every integer type as LLSD::Integer. +	template <class T> +	void add(const LLSD::String& name, const T& value, +			 typename boost::enable_if<boost::is_integral<T> >::type* = 0) +	{ +		mStats[name] = LLSD::Integer(value); +	} + +	// Store every floating-point type as LLSD::Real. +	template <class T> +	void add(const LLSD::String& name, const T& value, +			 typename boost::enable_if<boost::is_float<T> >::type* = 0) +	{ +		mStats[name] = LLSD::Real(value); +	} + +	// Hope that LLSD::Date values are sufficiently unambiguous. +	void add(const LLSD::String& name, const LLSD::Date& value) +	{ +		mStats[name] = value; +	} + +	LLSD get() const { return mStats; } + +private: +	LLSD mStats; +}; + +// Wrap boost::regex_match() with a function that doesn't throw. +template <typename S, typename M, typename R> +static bool regex_match_no_exc(const S& string, M& match, const R& regex) +{ +    try +    { +        return boost::regex_match(string, match, regex); +    } +    catch (const std::runtime_error& e) +    { +        LL_WARNS("LLMemoryInfo") << "error matching with '" << regex.str() << "': " +                                 << e.what() << ":\n'" << string << "'" << LL_ENDL; +        return false; +    } +} + +// Wrap boost::regex_search() with a function that doesn't throw. +template <typename S, typename M, typename R> +static bool regex_search_no_exc(const S& string, M& match, const R& regex) +{ +    try +    { +        return boost::regex_search(string, match, regex); +    } +    catch (const std::runtime_error& e) +    { +        LL_WARNS("LLMemoryInfo") << "error searching with '" << regex.str() << "': " +                                 << e.what() << ":\n'" << string << "'" << LL_ENDL; +        return false; +    } +} +  LLMemoryInfo::LLMemoryInfo()  { +	refresh();  }  #if LL_WINDOWS @@ -638,11 +742,7 @@ static U32 LLMemoryAdjustKBResult(U32 inKB)  U32 LLMemoryInfo::getPhysicalMemoryKB() const  {  #if LL_WINDOWS -	MEMORYSTATUSEX state; -	state.dwLength = sizeof(state); -	GlobalMemoryStatusEx(&state); - -	return LLMemoryAdjustKBResult((U32)(state.ullTotalPhys >> 10)); +	return LLMemoryAdjustKBResult(mStatsMap["Total Physical KB"].asInteger());  #elif LL_DARWIN  	// This might work on Linux as well.  Someone check... @@ -690,12 +790,82 @@ U32 LLMemoryInfo::getPhysicalMemoryClamped() const  void LLMemoryInfo::getAvailableMemoryKB(U32& avail_physical_mem_kb, U32& avail_virtual_mem_kb)  {  #if LL_WINDOWS -	MEMORYSTATUSEX state; -	state.dwLength = sizeof(state); -	GlobalMemoryStatusEx(&state); +	// Sigh, this shouldn't be a static method, then we wouldn't have to +	// reload this data separately from refresh() +	LLSD statsMap(loadStatsMap()); + +	avail_physical_mem_kb = statsMap["Avail Physical KB"].asInteger(); +	avail_virtual_mem_kb  = statsMap["Avail Virtual KB"].asInteger(); + +#elif LL_DARWIN +	// mStatsMap is derived from vm_stat, look for (e.g.) "kb free": +	// $ vm_stat +	// Mach Virtual Memory Statistics: (page size of 4096 bytes) +	// Pages free:                   462078. +	// Pages active:                 142010. +	// Pages inactive:               220007. +	// Pages wired down:             159552. +	// "Translation faults":      220825184. +	// Pages copy-on-write:         2104153. +	// Pages zero filled:         167034876. +	// Pages reactivated:             65153. +	// Pageins:                     2097212. +	// Pageouts:                      41759. +	// Object cache: 841598 hits of 7629869 lookups (11% hit rate) +	avail_physical_mem_kb = -1 ; +	avail_virtual_mem_kb = -1 ; -	avail_physical_mem_kb = (U32)(state.ullAvailPhys/1024) ; -	avail_virtual_mem_kb = (U32)(state.ullAvailVirtual/1024) ; +#elif LL_LINUX +	// mStatsMap is derived from MEMINFO_FILE: +	// $ cat /proc/meminfo +	// MemTotal:        4108424 kB +	// MemFree:         1244064 kB +	// Buffers:           85164 kB +	// Cached:          1990264 kB +	// SwapCached:            0 kB +	// Active:          1176648 kB +	// Inactive:        1427532 kB +	// Active(anon):     529152 kB +	// Inactive(anon):    15924 kB +	// Active(file):     647496 kB +	// Inactive(file):  1411608 kB +	// Unevictable:          16 kB +	// Mlocked:              16 kB +	// HighTotal:       3266316 kB +	// HighFree:         721308 kB +	// LowTotal:         842108 kB +	// LowFree:          522756 kB +	// SwapTotal:       6384632 kB +	// SwapFree:        6384632 kB +	// Dirty:                28 kB +	// Writeback:             0 kB +	// AnonPages:        528820 kB +	// Mapped:            89472 kB +	// Shmem:             16324 kB +	// Slab:             159624 kB +	// SReclaimable:     145168 kB +	// SUnreclaim:        14456 kB +	// KernelStack:        2560 kB +	// PageTables:         5560 kB +	// NFS_Unstable:          0 kB +	// Bounce:                0 kB +	// WritebackTmp:          0 kB +	// CommitLimit:     8438844 kB +	// Committed_AS:    1271596 kB +	// VmallocTotal:     122880 kB +	// VmallocUsed:       65252 kB +	// VmallocChunk:      52356 kB +	// HardwareCorrupted:     0 kB +	// HugePages_Total:       0 +	// HugePages_Free:        0 +	// HugePages_Rsvd:        0 +	// HugePages_Surp:        0 +	// Hugepagesize:       2048 kB +	// DirectMap4k:      434168 kB +	// DirectMap2M:      477184 kB +	// (could also run 'free', but easier to read a file than run a program) +	avail_physical_mem_kb = -1 ; +	avail_virtual_mem_kb = -1 ;  #else  	//do not know how to collect available memory info for other systems. @@ -708,56 +878,389 @@ void LLMemoryInfo::getAvailableMemoryKB(U32& avail_physical_mem_kb, U32& avail_v  void LLMemoryInfo::stream(std::ostream& s) const  { +	// We want these memory stats to be easy to grep from the log, along with +	// the timestamp. So preface each line with the timestamp and a +	// distinctive marker. Without that, we'd have to search the log for the +	// introducer line, then read subsequent lines, etc... +	std::string pfx(LLError::utcTime() + " <mem> "); + +	// Max key length +	size_t key_width(0); +	BOOST_FOREACH(const MapEntry& pair, inMap(mStatsMap)) +	{ +		size_t len(pair.first.length()); +		if (len > key_width) +		{ +			key_width = len; +		} +	} + +	// Now stream stats +	BOOST_FOREACH(const MapEntry& pair, inMap(mStatsMap)) +	{ +		s << pfx << std::setw(key_width+1) << (pair.first + ':') << ' '; +		LLSD value(pair.second); +		if (value.isInteger()) +			s << std::setw(12) << value.asInteger(); +		else if (value.isReal()) +			s << std::fixed << std::setprecision(1) << value.asReal(); +		else if (value.isDate()) +			value.asDate().toStream(s); +		else +			s << value;           // just use default LLSD formatting +		s << std::endl; +	} +} + +LLSD LLMemoryInfo::getStatsMap() const +{ +	return mStatsMap; +} + +LLMemoryInfo& LLMemoryInfo::refresh() +{ +	mStatsMap = loadStatsMap(); + +	LL_DEBUGS("LLMemoryInfo") << "Populated mStatsMap:\n"; +	LLSDSerialize::toPrettyXML(mStatsMap, LL_CONT); +	LL_ENDL; + +	return *this; +} + +LLSD LLMemoryInfo::loadStatsMap() +{ +	// This implementation is derived from stream() code (as of 2011-06-29). +	Stats stats; + +	// associate timestamp for analysis over time +	stats.add("timestamp", LLDate::now()); +  #if LL_WINDOWS  	MEMORYSTATUSEX state;  	state.dwLength = sizeof(state);  	GlobalMemoryStatusEx(&state); -	s << "Percent Memory use: " << (U32)state.dwMemoryLoad << '%' << std::endl; -	s << "Total Physical KB:  " << (U32)(state.ullTotalPhys/1024) << std::endl; -	s << "Avail Physical KB:  " << (U32)(state.ullAvailPhys/1024) << std::endl; -	s << "Total page KB:      " << (U32)(state.ullTotalPageFile/1024) << std::endl; -	s << "Avail page KB:      " << (U32)(state.ullAvailPageFile/1024) << std::endl; -	s << "Total Virtual KB:   " << (U32)(state.ullTotalVirtual/1024) << std::endl; -	s << "Avail Virtual KB:   " << (U32)(state.ullAvailVirtual/1024) << std::endl; +	stats.add("Percent Memory use", state.dwMemoryLoad); +	stats.add("Total Physical KB",  state.ullTotalPhys/1024); +	stats.add("Avail Physical KB",  state.ullAvailPhys/1024); +	stats.add("Total page KB",      state.ullTotalPageFile/1024); +	stats.add("Avail page KB",      state.ullAvailPageFile/1024); +	stats.add("Total Virtual KB",   state.ullTotalVirtual/1024); +	stats.add("Avail Virtual KB",   state.ullAvailVirtual/1024); + +	PERFORMANCE_INFORMATION perf; +	perf.cb = sizeof(perf); +	GetPerformanceInfo(&perf, sizeof(perf)); + +	SIZE_T pagekb(perf.PageSize/1024); +	stats.add("CommitTotal KB",     perf.CommitTotal * pagekb); +	stats.add("CommitLimit KB",     perf.CommitLimit * pagekb); +	stats.add("CommitPeak KB",      perf.CommitPeak * pagekb); +	stats.add("PhysicalTotal KB",   perf.PhysicalTotal * pagekb); +	stats.add("PhysicalAvail KB",   perf.PhysicalAvailable * pagekb); +	stats.add("SystemCache KB",     perf.SystemCache * pagekb); +	stats.add("KernelTotal KB",     perf.KernelTotal * pagekb); +	stats.add("KernelPaged KB",     perf.KernelPaged * pagekb); +	stats.add("KernelNonpaged KB",  perf.KernelNonpaged * pagekb); +	stats.add("PageSize KB",        pagekb); +	stats.add("HandleCount",        perf.HandleCount); +	stats.add("ProcessCount",       perf.ProcessCount); +	stats.add("ThreadCount",        perf.ThreadCount); + +	PROCESS_MEMORY_COUNTERS_EX pmem; +	pmem.cb = sizeof(pmem); +	// GetProcessMemoryInfo() is documented to accept either +	// PROCESS_MEMORY_COUNTERS* or PROCESS_MEMORY_COUNTERS_EX*, presumably +	// using the redundant size info to distinguish. But its prototype +	// specifically accepts PROCESS_MEMORY_COUNTERS*, and since this is a +	// classic-C API, PROCESS_MEMORY_COUNTERS_EX isn't a subclass. Cast the +	// pointer. +	GetProcessMemoryInfo(GetCurrentProcess(), PPROCESS_MEMORY_COUNTERS(&pmem), sizeof(pmem)); + +	stats.add("Page Fault Count",              pmem.PageFaultCount); +	stats.add("PeakWorkingSetSize KB",         pmem.PeakWorkingSetSize/1024); +	stats.add("WorkingSetSize KB",             pmem.WorkingSetSize/1024); +	stats.add("QutaPeakPagedPoolUsage KB",     pmem.QuotaPeakPagedPoolUsage/1024); +	stats.add("QuotaPagedPoolUsage KB",        pmem.QuotaPagedPoolUsage/1024); +	stats.add("QuotaPeakNonPagedPoolUsage KB", pmem.QuotaPeakNonPagedPoolUsage/1024); +	stats.add("QuotaNonPagedPoolUsage KB",     pmem.QuotaNonPagedPoolUsage/1024); +	stats.add("PagefileUsage KB",              pmem.PagefileUsage/1024); +	stats.add("PeakPagefileUsage KB",          pmem.PeakPagefileUsage/1024); +	stats.add("PrivateUsage KB",               pmem.PrivateUsage/1024); +  #elif LL_DARWIN  	uint64_t phys = 0;  	size_t len = sizeof(phys);	 -	if(sysctlbyname("hw.memsize", &phys, &len, NULL, 0) == 0) +	if (sysctlbyname("hw.memsize", &phys, &len, NULL, 0) == 0)  	{ -		s << "Total Physical KB:  " << phys/1024 << std::endl; +		stats.add("Total Physical KB", phys/1024);  	}  	else  	{ -		s << "Unable to collect memory information"; +		LL_WARNS("LLMemoryInfo") << "Unable to collect hw.memsize memory information" << LL_ENDL; +	} + +	FILE* pout = popen("vm_stat 2>&1", "r"); +	if (! pout)                     // popen() couldn't run vm_stat +	{ +		// Save errno right away. +		int popen_errno(errno); +		LL_WARNS("LLMemoryInfo") << "Unable to collect vm_stat memory information: "; +		char buffer[256]; +		if (0 == strerror_r(popen_errno, buffer, sizeof(buffer))) +		{ +			LL_CONT << buffer; +		} +		else +		{ +			LL_CONT << "errno " << popen_errno; +		} +		LL_CONT << LL_ENDL; +	} +	else                            // popen() launched vm_stat +	{ +		// Mach Virtual Memory Statistics: (page size of 4096 bytes) +		// Pages free:					 462078. +		// Pages active:				 142010. +		// Pages inactive:				 220007. +		// Pages wired down:			 159552. +		// "Translation faults":	  220825184. +		// Pages copy-on-write:			2104153. +		// Pages zero filled:		  167034876. +		// Pages reactivated:			  65153. +		// Pageins:						2097212. +		// Pageouts:					  41759. +		// Object cache: 841598 hits of 7629869 lookups (11% hit rate) + +		// Intentionally don't pass the boost::no_except flag. These +		// boost::regex objects are constructed with string literals, so they +		// should be valid every time. If they become invalid, we WANT an +		// exception, hopefully even before the dev checks in. +		boost::regex pagesize_rx("\\(page size of ([0-9]+) bytes\\)"); +		boost::regex stat_rx("(.+): +([0-9]+)\\."); +		boost::regex cache_rx("Object cache: ([0-9]+) hits of ([0-9]+) lookups " +							  "\\(([0-9]+)% hit rate\\)"); +		boost::cmatch matched; +		LLSD::Integer pagesizekb(4096/1024); + +		// Here 'pout' is vm_stat's stdout. Search it for relevant data. +		char line[100]; +		line[sizeof(line)-1] = '\0'; +		while (fgets(line, sizeof(line)-1, pout)) +		{ +			size_t linelen(strlen(line)); +			// Truncate any trailing newline +			if (line[linelen - 1] == '\n') +			{ +				line[--linelen] = '\0'; +			} +			LL_DEBUGS("LLMemoryInfo") << line << LL_ENDL; +			if (regex_search_no_exc(line, matched, pagesize_rx)) +			{ +				// "Mach Virtual Memory Statistics: (page size of 4096 bytes)" +				std::string pagesize_str(matched[1].first, matched[1].second); +				try +				{ +					// Reasonable to assume that pagesize will always be a +					// multiple of 1Kb? +					pagesizekb = boost::lexical_cast<LLSD::Integer>(pagesize_str)/1024; +				} +				catch (const boost::bad_lexical_cast&) +				{ +					LL_WARNS("LLMemoryInfo") << "couldn't parse '" << pagesize_str +											 << "' in vm_stat line: " << line << LL_ENDL; +					continue; +				} +				stats.add("page size", pagesizekb); +			} +			else if (regex_match_no_exc(line, matched, stat_rx)) +			{ +				// e.g. "Pages free:					 462078." +				// Strip double-quotes off certain statistic names +				const char *key_begin(matched[1].first), *key_end(matched[1].second); +				if (key_begin[0] == '"' && key_end[-1] == '"') +				{ +					++key_begin; +					--key_end; +				} +				LLSD::String key(key_begin, key_end); +				LLSD::String value_str(matched[2].first, matched[2].second); +				LLSD::Integer value(0); +				try +				{ +					value = boost::lexical_cast<LLSD::Integer>(value_str); +				} +				catch (const boost::bad_lexical_cast&) +				{ +					LL_WARNS("LLMemoryInfo") << "couldn't parse '" << value_str +											 << "' in vm_stat line: " << line << LL_ENDL; +					continue; +				} +				// Store this statistic. +				stats.add(key, value); +				// Is this in units of pages? If so, convert to Kb. +				static const LLSD::String pages("Pages "); +				if (key.substr(0, pages.length()) == pages) +				{ +					// Synthesize a new key with kb in place of Pages +					LLSD::String kbkey("kb "); +					kbkey.append(key.substr(pages.length())); +					stats.add(kbkey, value * pagesizekb); +				} +			} +			else if (regex_match_no_exc(line, matched, cache_rx)) +			{ +				// e.g. "Object cache: 841598 hits of 7629869 lookups (11% hit rate)" +				static const char* cache_keys[] = { "cache hits", "cache lookups", "cache hit%" }; +				std::vector<LLSD::Integer> cache_values; +				for (size_t i = 0; i < (sizeof(cache_keys)/sizeof(cache_keys[0])); ++i) +				{ +					LLSD::String value_str(matched[i+1].first, matched[i+1].second); +					LLSD::Integer value(0); +					try +					{ +						value = boost::lexical_cast<LLSD::Integer>(value_str); +					} +					catch (boost::bad_lexical_cast&) +					{ +						LL_WARNS("LLMemoryInfo") << "couldn't parse '" << value_str +												 << "' in vm_stat line: " << line << LL_ENDL; +						continue; +					} +					stats.add(cache_keys[i], value); +				} +			} +			else +			{ +				LL_WARNS("LLMemoryInfo") << "unrecognized vm_stat line: " << line << LL_ENDL; +			} +		} +		int status(pclose(pout)); +		if (status == -1)           // pclose() couldn't retrieve rc +		{ +			// Save errno right away. +			int pclose_errno(errno); +			// The ECHILD error happens so frequently that unless filtered, +			// the warning below spams the log file. This is too bad, because +			// sometimes the logic above fails to produce any output derived +			// from vm_stat, but we've been unable to observe any specific +			// error indicating the problem. +			if (pclose_errno != ECHILD) +			{ +				LL_WARNS("LLMemoryInfo") << "Unable to obtain vm_stat termination code: "; +				char buffer[256]; +				if (0 == strerror_r(pclose_errno, buffer, sizeof(buffer))) +				{ +					LL_CONT << buffer; +				} +				else +				{ +					LL_CONT << "errno " << pclose_errno; +				} +				LL_CONT << LL_ENDL; +			} +		} +		else                        // pclose() retrieved rc; analyze +		{ +			if (WIFEXITED(status)) +			{ +				int rc(WEXITSTATUS(status)); +				if (rc != 0) +				{ +					LL_WARNS("LLMemoryInfo") << "vm_stat terminated with rc " << rc << LL_ENDL; +				} +			} +			else if (WIFSIGNALED(status)) +			{ +				LL_WARNS("LLMemoryInfo") << "vm_stat terminated by signal " << WTERMSIG(status) +										 << LL_ENDL; +			} +		}  	} +  #elif LL_SOLARIS -        U64 phys = 0; +	U64 phys = 0; -        phys = (U64)(sysconf(_SC_PHYS_PAGES)) * (U64)(sysconf(_SC_PAGESIZE)/1024); +	phys = (U64)(sysconf(_SC_PHYS_PAGES)) * (U64)(sysconf(_SC_PAGESIZE)/1024); -        s << "Total Physical KB:  " << phys << std::endl; -#else -	// *NOTE: This works on linux. What will it do on other systems? -	LLFILE* meminfo = LLFile::fopen(MEMINFO_FILE,"rb"); -	if(meminfo) +	stats.add("Total Physical KB", phys); + +#elif LL_LINUX +	std::ifstream meminfo(MEMINFO_FILE); +	if (meminfo.is_open())  	{ -		char line[MAX_STRING];		/* Flawfinder: ignore */ -		memset(line, 0, MAX_STRING); -		while(fgets(line, MAX_STRING, meminfo)) +		// MemTotal:		4108424 kB +		// MemFree:			1244064 kB +		// Buffers:			  85164 kB +		// Cached:			1990264 kB +		// SwapCached:			  0 kB +		// Active:			1176648 kB +		// Inactive:		1427532 kB +		// ... +		// VmallocTotal:	 122880 kB +		// VmallocUsed:		  65252 kB +		// VmallocChunk:	  52356 kB +		// HardwareCorrupted:	  0 kB +		// HugePages_Total:		  0 +		// HugePages_Free:		  0 +		// HugePages_Rsvd:		  0 +		// HugePages_Surp:		  0 +		// Hugepagesize:	   2048 kB +		// DirectMap4k:		 434168 kB +		// DirectMap2M:		 477184 kB + +		// Intentionally don't pass the boost::no_except flag. This +		// boost::regex object is constructed with a string literal, so it +		// should be valid every time. If it becomes invalid, we WANT an +		// exception, hopefully even before the dev checks in. +		boost::regex stat_rx("(.+): +([0-9]+)( kB)?"); +		boost::smatch matched; + +		std::string line; +		while (std::getline(meminfo, line))  		{ -			line[strlen(line)-1] = ' ';		 /*Flawfinder: ignore*/ -			s << line; +			LL_DEBUGS("LLMemoryInfo") << line << LL_ENDL; +			if (regex_match_no_exc(line, matched, stat_rx)) +			{ +				// e.g. "MemTotal:		4108424 kB" +				LLSD::String key(matched[1].first, matched[1].second); +				LLSD::String value_str(matched[2].first, matched[2].second); +				LLSD::Integer value(0); +				try +				{ +					value = boost::lexical_cast<LLSD::Integer>(value_str); +				} +				catch (const boost::bad_lexical_cast&) +				{ +					LL_WARNS("LLMemoryInfo") << "couldn't parse '" << value_str +											 << "' in " << MEMINFO_FILE << " line: " +											 << line << LL_ENDL; +					continue; +				} +				// Store this statistic. +				stats.add(key, value); +			} +			else +			{ +				LL_WARNS("LLMemoryInfo") << "unrecognized " << MEMINFO_FILE << " line: " +										 << line << LL_ENDL; +			}  		} -		fclose(meminfo);  	}  	else  	{ -		s << "Unable to collect memory information"; +		LL_WARNS("LLMemoryInfo") << "Unable to collect memory information" << LL_ENDL;  	} + +#else +	LL_WARNS("LLMemoryInfo") << "Unknown system; unable to collect memory information" << LL_ENDL; +  #endif + +	return stats.get();  }  std::ostream& operator<<(std::ostream& s, const LLOSInfo& info) @@ -778,6 +1281,143 @@ std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info)  	return s;  } +class FrameWatcher +{ +public: +    FrameWatcher(): +        // Hooking onto the "mainloop" event pump gets us one call per frame. +        mConnection(LLEventPumps::instance() +                    .obtain("mainloop") +                    .listen("FrameWatcher", boost::bind(&FrameWatcher::tick, this, _1))), +        // Initializing mSampleStart to an invalid timestamp alerts us to skip +        // trying to compute framerate on the first call. +        mSampleStart(-1), +        // Initializing mSampleEnd to 0 ensures that we treat the first call +        // as the completion of a sample window. +        mSampleEnd(0), +        mFrames(0), +        // Both MEM_INFO_WINDOW and MEM_INFO_THROTTLE are in seconds. We need +        // the number of integer MEM_INFO_THROTTLE sample slots that will fit +        // in MEM_INFO_WINDOW. Round up. +        mSamples(int((MEM_INFO_WINDOW / MEM_INFO_THROTTLE) + 0.7)), +        // Initializing to F32_MAX means that the first real frame will become +        // the slowest ever, which sounds like a good idea. +        mSlowest(F32_MAX) +    {} + +    bool tick(const LLSD&) +    { +        F32 timestamp(mTimer.getElapsedTimeF32()); + +        // Count this frame in the interval just completed. +        ++mFrames; + +        // Have we finished a sample window yet? +        if (timestamp < mSampleEnd) +        { +            // no, just keep waiting +            return false; +        } + +        // Set up for next sample window. Capture values for previous frame in +        // local variables and reset data members. +        U32 frames(mFrames); +        F32 sampleStart(mSampleStart); +        // No frames yet in next window +        mFrames = 0; +        // which starts right now +        mSampleStart = timestamp; +        // and ends MEM_INFO_THROTTLE seconds in the future +        mSampleEnd = mSampleStart + MEM_INFO_THROTTLE; + +        // On the very first call, that's all we can do, no framerate +        // computation is possible. +        if (sampleStart < 0) +        { +            return false; +        } + +        // How long did this actually take? As framerate slows, the duration +        // of the frame we just finished could push us WELL beyond our desired +        // sample window size. +        F32 elapsed(timestamp - sampleStart); +        F32 framerate(frames/elapsed); + +        // Remember previous slowest framerate because we're just about to +        // update it. +        F32 slowest(mSlowest); +        // Remember previous number of samples. +        boost::circular_buffer<F32>::size_type prevSize(mSamples.size()); + +        // Capture new framerate in our samples buffer. Once the buffer is +        // full (after MEM_INFO_WINDOW seconds), this will displace the oldest +        // sample. ("So they all rolled over, and one fell out...") +        mSamples.push_back(framerate); + +        // Calculate the new minimum framerate. I know of no way to update a +        // rolling minimum without ever rescanning the buffer. But since there +        // are only a few tens of items in this buffer, rescanning it is +        // probably cheaper (and certainly easier to reason about) than +        // attempting to optimize away some of the scans. +        mSlowest = framerate;       // pick an arbitrary entry to start +        for (boost::circular_buffer<F32>::const_iterator si(mSamples.begin()), send(mSamples.end()); +             si != send; ++si) +        { +            if (*si < mSlowest) +            { +                mSlowest = *si; +            } +        } + +        // We're especially interested in memory as framerate drops. Only log +        // when framerate drops below the slowest framerate we remember. +        // (Should always be true for the end of the very first sample +        // window.) +        if (framerate >= slowest) +        { +            return false; +        } +        // Congratulations, we've hit a new low.  :-P + +        LL_INFOS("FrameWatcher") << ' '; +        if (! prevSize) +        { +            LL_CONT << "initial framerate "; +        } +        else +        { +            LL_CONT << "slowest framerate for last " << int(prevSize * MEM_INFO_THROTTLE) +                    << " seconds "; +        } +        LL_CONT << std::fixed << std::setprecision(1) << framerate << '\n' +                << LLMemoryInfo() << LL_ENDL; + +        return false; +    } + +private: +    // Storing the connection in an LLTempBoundListener ensures it will be +    // disconnected when we're destroyed. +    LLTempBoundListener mConnection; +    // Track elapsed time +    LLTimer mTimer; +    // Some of what you see here is in fact redundant with functionality you +    // can get from LLTimer. Unfortunately the LLTimer API is missing the +    // feature we need: has at least the stated interval elapsed, and if so, +    // exactly how long has passed? So we have to do it by hand, sigh. +    // Time at start, end of sample window +    F32 mSampleStart, mSampleEnd; +    // Frames this sample window +    U32 mFrames; +    // Sliding window of framerate samples +    boost::circular_buffer<F32> mSamples; +    // Slowest framerate in mSamples +    F32 mSlowest; +}; + +// Need an instance of FrameWatcher before it does any good +static FrameWatcher sFrameWatcher; +  BOOL gunzip_file(const std::string& srcfile, const std::string& dstfile)  {  	std::string tmpfile; diff --git a/indra/llcommon/llsys.h b/indra/llcommon/llsys.h index 41a4f25000..739e795d3a 100644 --- a/indra/llcommon/llsys.h +++ b/indra/llcommon/llsys.h @@ -36,6 +36,7 @@  //  llinfos << info << llendl;  // +#include "llsd.h"  #include <iosfwd>  #include <string> @@ -117,6 +118,27 @@ public:  	//get the available memory infomation in KiloBytes.  	static void getAvailableMemoryKB(U32& avail_physical_mem_kb, U32& avail_virtual_mem_kb); + +	// Retrieve a map of memory statistics. The keys of the map are platform- +	// dependent. The values are in kilobytes to try to avoid integer overflow. +	LLSD getStatsMap() const; + +	// Re-fetch memory data (as reported by stream() and getStatsMap()) from the +	// system. Normally this is fetched at construction time. Return (*this) +	// to permit usage of the form: +	// @code +	// LLMemoryInfo info; +	// ... +	// info.refresh().getStatsMap(); +	// @endcode +	LLMemoryInfo& refresh(); + +private: +	// set mStatsMap +	static LLSD loadStatsMap(); + +	// Memory stats for getStatsMap(). +	LLSD mStatsMap;  }; diff --git a/indra/llcommon/llversionviewer.h b/indra/llcommon/llversionviewer.h index 3dcaca4f16..0018b8e844 100644 --- a/indra/llcommon/llversionviewer.h +++ b/indra/llcommon/llversionviewer.h @@ -29,7 +29,7 @@  const S32 LL_VERSION_MAJOR = 2;  const S32 LL_VERSION_MINOR = 8; -const S32 LL_VERSION_PATCH = 0; +const S32 LL_VERSION_PATCH = 1;  const S32 LL_VERSION_BUILD = 0;  const char * const LL_CHANNEL = "Second Life Developer"; diff --git a/indra/llmath/CMakeLists.txt b/indra/llmath/CMakeLists.txt index 9dadad7dd3..cd100cdf9f 100644 --- a/indra/llmath/CMakeLists.txt +++ b/indra/llmath/CMakeLists.txt @@ -12,6 +12,8 @@ include_directories(  set(llmath_SOURCE_FILES      llbbox.cpp      llbboxlocal.cpp +    llcalc.cpp +    llcalcparser.cpp      llcamera.cpp      llcoordframe.cpp      llline.cpp @@ -46,6 +48,8 @@ set(llmath_HEADER_FILES      coordframe.h      llbbox.h      llbboxlocal.h +    llcalc.h +    llcalcparser.h      llcamera.h      llcoord.h      llcoordframe.h diff --git a/indra/llmath/llcalc.cpp b/indra/llmath/llcalc.cpp new file mode 100644 index 0000000000..597d0815fb --- /dev/null +++ b/indra/llmath/llcalc.cpp @@ -0,0 +1,145 @@ +/* + *  LLCalc.cpp + *  SecondLife + * + *  Created by Aimee Walton on 28/09/2008. + *  Copyright 2008 Aimee Walton. + * + */ + +#include "linden_common.h" + +#include "llcalc.h" + +#include "llcalcparser.h" +#include "llmath.h" + + +// Variable names for use in the build floater +const char* LLCalc::X_POS = "PX"; +const char* LLCalc::Y_POS = "PY"; +const char* LLCalc::Z_POS = "PZ"; +const char* LLCalc::X_SCALE = "SX"; +const char* LLCalc::Y_SCALE = "SY"; +const char* LLCalc::Z_SCALE = "SZ"; +const char* LLCalc::X_ROT = "RX"; +const char* LLCalc::Y_ROT = "RY"; +const char* LLCalc::Z_ROT = "RZ"; +const char* LLCalc::HOLLOW = "HLW"; +const char* LLCalc::CUT_BEGIN = "CB"; +const char* LLCalc::CUT_END = "CE"; +const char* LLCalc::PATH_BEGIN = "PB"; +const char* LLCalc::PATH_END = "PE"; +const char* LLCalc::TWIST_BEGIN = "TB"; +const char* LLCalc::TWIST_END = "TE"; +const char* LLCalc::X_SHEAR = "SHX"; +const char* LLCalc::Y_SHEAR = "SHY"; +const char* LLCalc::X_TAPER = "TPX"; +const char* LLCalc::Y_TAPER = "TPY"; +const char* LLCalc::RADIUS_OFFSET = "ROF"; +const char* LLCalc::REVOLUTIONS = "REV"; +const char* LLCalc::SKEW = "SKW"; +const char* LLCalc::X_HOLE = "HLX"; +const char* LLCalc::Y_HOLE = "HLY"; +const char* LLCalc::TEX_U_SCALE = "TSU"; +const char* LLCalc::TEX_V_SCALE = "TSV"; +const char* LLCalc::TEX_U_OFFSET = "TOU"; +const char* LLCalc::TEX_V_OFFSET = "TOV"; +const char* LLCalc::TEX_ROTATION = "TROT"; +const char* LLCalc::TEX_TRANSPARENCY = "TRNS"; +const char* LLCalc::TEX_GLOW = "GLOW"; + + +LLCalc* LLCalc::sInstance = NULL; + +LLCalc::LLCalc() : mLastErrorPos(0) +{ +	// Init table of constants +	mConstants["PI"] = F_PI; +	mConstants["TWO_PI"] = F_TWO_PI; +	mConstants["PI_BY_TWO"] = F_PI_BY_TWO; +	mConstants["SQRT_TWO_PI"] = F_SQRT_TWO_PI; +	mConstants["SQRT2"] = F_SQRT2; +	mConstants["SQRT3"] = F_SQRT3; +	mConstants["DEG_TO_RAD"] = DEG_TO_RAD; +	mConstants["RAD_TO_DEG"] = RAD_TO_DEG; +	mConstants["GRAVITY"] = GRAVITY; +} + +LLCalc::~LLCalc() +{ +} + +//static +void LLCalc::cleanUp() +{ +	delete sInstance; +	sInstance = NULL; +} + +//static +LLCalc* LLCalc::getInstance() +{ +    if (!sInstance)	sInstance = new LLCalc(); +	return sInstance; +} + +void LLCalc::setVar(const std::string& name, const F32& value) +{ +	mVariables[name] = value; +} + +void LLCalc::clearVar(const std::string& name) +{ +	mVariables.erase(name); +} + +void LLCalc::clearAllVariables() +{ +	mVariables.clear(); +} + +/* +void LLCalc::updateVariables(LLSD& vars) +{ +	LLSD::map_iterator cIt = vars.beginMap(); +	for(; cIt != vars.endMap(); cIt++) +	{ +		setVar(cIt->first, (F32)(LLSD::Real)cIt->second); +	} +} +*/ + +bool LLCalc::evalString(const std::string& expression, F32& result) +{ +	std::string expr_upper = expression; +	LLStringUtil::toUpper(expr_upper); +	 +	LLCalcParser calc(result, &mConstants, &mVariables); + +	mLastErrorPos = 0; +	std::string::iterator start = expr_upper.begin(); + 	parse_info<std::string::iterator> info; +	 +	try +	{ +		info = parse(start, expr_upper.end(), calc, space_p); +		lldebugs << "Math expression: " << expression << " = " << result << llendl; +	} +	catch(parser_error<std::string, std::string::iterator> &e) +	{ +		mLastErrorPos = e.where - expr_upper.begin(); +		 +		llinfos << "Calc parser exception: " << e.descriptor << " at " << mLastErrorPos << " in expression: " << expression << llendl; +		return false; +	} +	 +	if (!info.full) +	{ +		mLastErrorPos = info.stop - expr_upper.begin(); +		llinfos << "Unhandled syntax error at " << mLastErrorPos << " in expression: " << expression << llendl; +		return false; +	} +	 +	return true; +} diff --git a/indra/llmath/llcalc.h b/indra/llmath/llcalc.h new file mode 100644 index 0000000000..cc31950cb6 --- /dev/null +++ b/indra/llmath/llcalc.h @@ -0,0 +1,83 @@ +/* + *  LLCalc.h + *  SecondLife + * + *  Created by Aimee Walton on 28/09/2008. + *  Copyright 2008 Aimee Walton. + * + */ + +#ifndef LL_CALC_H +#define LL_CALC_H + +#include <map> +#include <string> + +class LLCalc +{ +public: +	LLCalc(); +	~LLCalc(); + +	// Variable name constants +	static const char* X_POS; +	static const char* Y_POS; +	static const char* Z_POS; +	static const char* X_SCALE; +	static const char* Y_SCALE; +	static const char* Z_SCALE; +	static const char* X_ROT; +	static const char* Y_ROT; +	static const char* Z_ROT; +	static const char* HOLLOW; +	static const char* CUT_BEGIN; +	static const char* CUT_END; +	static const char* PATH_BEGIN; +	static const char* PATH_END; +	static const char* TWIST_BEGIN; +	static const char* TWIST_END; +	static const char* X_SHEAR; +	static const char* Y_SHEAR; +	static const char* X_TAPER; +	static const char* Y_TAPER; +	static const char* RADIUS_OFFSET; +	static const char* REVOLUTIONS; +	static const char* SKEW; +	static const char* X_HOLE; +	static const char* Y_HOLE; +	static const char* TEX_U_SCALE; +	static const char* TEX_V_SCALE; +	static const char* TEX_U_OFFSET; +	static const char* TEX_V_OFFSET; +	static const char* TEX_ROTATION; +	static const char* TEX_TRANSPARENCY; +	static const char* TEX_GLOW; + +	void	setVar(const std::string& name, const F32& value); +	void	clearVar(const std::string& name); +	void	clearAllVariables(); +//	void	updateVariables(LLSD& vars); + +	bool	evalString(const std::string& expression, F32& result); +	std::string::size_type	getLastErrorPos()	{ return mLastErrorPos; } +	 +	static LLCalc* getInstance(); +	static void cleanUp(); + +	typedef	std::map<std::string, F32> calc_map_t; +	 +private: +	std::string::size_type	mLastErrorPos; +	 +	calc_map_t	mConstants; +	calc_map_t	mVariables; +	 +	// *TODO: Add support for storing user defined variables, and stored functions. +	//	Will need UI work, and a means to save them between sessions. +//	calc_map_t mUserVariables; +	 +	// "There shall be only one" +	static LLCalc*	sInstance; +}; + +#endif // LL_CALC_H diff --git a/indra/llmath/llcalcparser.cpp b/indra/llmath/llcalcparser.cpp new file mode 100644 index 0000000000..fd55376fa9 --- /dev/null +++ b/indra/llmath/llcalcparser.cpp @@ -0,0 +1,46 @@ +/* + *  LLCalcParser.cpp + *  SecondLife + * + *  Created by Aimee Walton on 28/09/2008. + *  Copyright 2008 Aimee Walton. + * + */ + +#include "linden_common.h" + +#include "llcalcparser.h" +using namespace boost::spirit::classic; + +F32 LLCalcParser::lookup(const std::string::iterator& start, const std::string::iterator& end) const +{ +	LLCalc::calc_map_t::iterator iter; + +	std::string name(start, end); +	 +	if (mConstants) +	{ +		iter = mConstants->find(name); +		if (iter != mConstants->end()) +		{ +			return (*iter).second; +		} +	} +	else +	{ +		// This should never happen! +		throw_(end, std::string("Missing constants table")); +	} +	 +	if (mVariables) +	{ +		iter = mVariables->find(name); +		if (iter != mVariables->end()) +		{ +			return (*iter).second; +		} +	} +	 +	throw_(end, std::string("Unknown symbol " + name)); +	return 0.f; +} diff --git a/indra/llmath/llcalcparser.h b/indra/llmath/llcalcparser.h new file mode 100644 index 0000000000..600e173661 --- /dev/null +++ b/indra/llmath/llcalcparser.h @@ -0,0 +1,174 @@ +/* + *  LLCalcParser.h + *  SecondLife + * + *  Created by Aimee Walton on 28/09/2008. + *  Copyright 2008 Aimee Walton. + * + */ + +#ifndef LL_CALCPARSER_H +#define LL_CALCPARSER_H + +#include <boost/spirit/include/classic_attribute.hpp> +#include <boost/spirit/include/classic_core.hpp> +#include <boost/spirit/include/classic_error_handling.hpp> +#include <boost/spirit/include/classic_position_iterator.hpp> +#include <boost/spirit/include/phoenix1_binders.hpp> +#include <boost/spirit/include/classic_symbols.hpp> +using namespace boost::spirit::classic; + +#include "llcalc.h" +#include "llmath.h" + +struct LLCalcParser : grammar<LLCalcParser> +{ +	LLCalcParser(F32& result, LLCalc::calc_map_t* constants, LLCalc::calc_map_t* vars) : +		mResult(result), mConstants(constants), mVariables(vars) {}; +	 +	struct value_closure : closure<value_closure, F32> +	{ +		member1 value; +	}; +	 +	template <typename ScannerT> +	struct definition +	{ +		// Rule declarations +		rule<ScannerT> statement, identifier; +		rule<ScannerT, value_closure::context_t> expression, term, +			power,  +			unary_expr,  +			factor,  +			unary_func,  +			binary_func, +			group; + +		// start() should return the starting symbol +		rule<ScannerT> const& start() const { return statement; } +		 +		definition(LLCalcParser const& self) +		{ +			using namespace phoenix; +			 +			assertion<std::string> assert_domain("Domain error"); +//			assertion<std::string> assert_symbol("Unknown symbol"); +			assertion<std::string> assert_syntax("Syntax error"); +			 +			identifier = +				lexeme_d[(alpha_p | '_') >> *(alnum_p | '_')] +			; +			 +			group = +				'(' >> expression[group.value = arg1] >> assert_syntax(ch_p(')')) +			; + +			unary_func = +				((str_p("SIN") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_sin)(self,arg1)]) | +				 (str_p("COS") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_cos)(self,arg1)]) | +				 (str_p("TAN") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_tan)(self,arg1)]) | +				 (str_p("ASIN") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_asin)(self,arg1)]) | +				 (str_p("ACOS") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_acos)(self,arg1)]) | +				 (str_p("ATAN") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_atan)(self,arg1)]) | +				 (str_p("SQRT") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_sqrt)(self,arg1)]) | +				 (str_p("LOG") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_log)(self,arg1)]) | +				 (str_p("EXP") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_exp)(self,arg1)]) | +				 (str_p("ABS") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_fabs)(self,arg1)]) | +				 (str_p("FLR") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_floor)(self,arg1)]) | +				 (str_p("CEIL") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_ceil)(self,arg1)]) +				) >> assert_syntax(ch_p(')')) +			; +			 +			binary_func = +				((str_p("ATAN2") >> '(' >> expression[binary_func.value = arg1] >> ',' >> +				  expression[binary_func.value = bind(&LLCalcParser::_atan2)(self, binary_func.value, arg1)]) | +				 (str_p("MIN") >> '(' >> expression[binary_func.value = arg1] >> ',' >>  +				  expression[binary_func.value = bind(&LLCalcParser::_min)(self, binary_func.value, arg1)]) | +				 (str_p("MAX") >> '(' >> expression[binary_func.value = arg1] >> ',' >>  +				  expression[binary_func.value = bind(&LLCalcParser::_max)(self, binary_func.value, arg1)]) +				) >> assert_syntax(ch_p(')')) +			; +			 +			// *TODO: Localisation of the decimal point? +			// Problem, LLLineEditor::postvalidateFloat accepts a comma when appropriate +			// for the current locale. However to do that here could clash with using +			// the comma as a separator when passing arguments to functions. +			factor = +				(ureal_p[factor.value = arg1] | +				 group[factor.value = arg1] | +				 unary_func[factor.value = arg1] | +				 binary_func[factor.value = arg1] | +				 // Lookup throws an Unknown Symbol error if it is unknown, while this works fine, +				 // would be "neater" to handle symbol lookup from here with an assertive parser. +//				 constants_p[factor.value = arg1]| +				 identifier[factor.value = bind(&LLCalcParser::lookup)(self, arg1, arg2)] +				) >> +				// Detect and throw math errors. +				assert_domain(eps_p(bind(&LLCalcParser::checkNaN)(self, factor.value))) +			; + +			unary_expr = +				!ch_p('+') >> factor[unary_expr.value = arg1] | +				'-' >> factor[unary_expr.value = -arg1] +			; +			 +			power = +				unary_expr[power.value = arg1] >> +				*('^' >> assert_syntax(unary_expr[power.value = bind(&powf)(power.value, arg1)])) +			; +			 +			term = +				power[term.value = arg1] >> +				*(('*' >> assert_syntax(power[term.value *= arg1])) | +				  ('/' >> assert_syntax(power[term.value /= arg1])) | +				  ('%' >> assert_syntax(power[term.value = bind(&fmodf)(term.value, arg1)])) +				) +			; +			 +			expression = +				assert_syntax(term[expression.value = arg1]) >> +				*(('+' >> assert_syntax(term[expression.value += arg1])) | +				  ('-' >> assert_syntax(term[expression.value -= arg1])) +				) +			; + +			statement = +				!ch_p('=') >> ( expression )[var(self.mResult) = arg1] >> (end_p) +			; +		} +	}; +	 +private: +	// Member functions for semantic actions +	F32	lookup(const std::string::iterator&, const std::string::iterator&) const; +	F32 _min(const F32& a, const F32& b) const { return llmin(a, b); } +	F32 _max(const F32& a, const F32& b) const { return llmax(a, b); } +	 +	bool checkNaN(const F32& a) const { return !llisnan(a); } +	 +	//FIX* non ambigious function fix making SIN() work for calc -Cryogenic Blitz +	F32 _sin(const F32& a) const { return sin(DEG_TO_RAD * a); } +	F32 _cos(const F32& a) const { return cos(DEG_TO_RAD * a); } +	F32 _tan(const F32& a) const { return tan(DEG_TO_RAD * a); } +	F32 _asin(const F32& a) const { return asin(a * RAD_TO_DEG); } +	F32 _acos(const F32& a) const { return acos(a * RAD_TO_DEG); } +	F32 _atan(const F32& a) const { return atan(a * RAD_TO_DEG); } +	F32 _sqrt(const F32& a) const { return sqrt(a); } +	F32 _log(const F32& a) const { return log(a); } +	F32 _exp(const F32& a) const { return exp(a); } +	F32 _fabs(const F32& a) const { return fabs(a); } +	F32 _floor(const F32& a) const { return llfloor(a); } +	F32 _ceil(const F32& a) const { return llceil(a); } + +	F32 _atan2(const F32& a,const F32& b) const { return atan2(a,b); } + + + +	LLCalc::calc_map_t* mConstants; +	LLCalc::calc_map_t* mVariables; +//	LLCalc::calc_map_t* mUserVariables; +	 +	F32&		mResult; +}; + +#endif // LL_CALCPARSER_H diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 123997e5e9..06fbc0f234 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -37,6 +37,7 @@  #include "llgl.h"  #include "lltimer.h" +#include "llcalc.h"  //#include "llclipboard.h"  #include "llcontrol.h"  #include "llbutton.h" @@ -133,6 +134,7 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p)  	mIgnoreTab( p.ignore_tab ),  	mDrawAsterixes( p.is_password ),  	mSelectAllonFocusReceived( p.select_on_focus ), +	mSelectAllonCommit( TRUE ),  	mPassDelete(FALSE),  	mReadOnly(FALSE),  	mBgImage( p.background_image ), @@ -230,7 +232,10 @@ void LLLineEditor::onCommit()  	setControlValue(getValue());  	LLUICtrl::onCommit(); -	selectAll(); + +	// Selection on commit needs to be turned off when evaluating maths +	// expressions, to allow indication of the error position +	if (mSelectAllonCommit) selectAll();  }  // Returns TRUE if user changed value at all @@ -2072,6 +2077,32 @@ BOOL LLLineEditor::postvalidateFloat(const std::string &str)  	return success;  } +BOOL LLLineEditor::evaluateFloat() +{ +	bool success; +	F32 result = 0.f; +	std::string expr = getText(); +	LLStringUtil::toUpper(expr); + +	success = LLCalc::getInstance()->evalString(expr, result); + +	if (!success) +	{ +		// Move the cursor to near the error on failure +		setCursor(LLCalc::getInstance()->getLastErrorPos()); +		// *TODO: Translated error message indicating the type of error? Select error text? +	} +	else +	{ +		// Replace the expression with the result +		std::string result_str = llformat("%f",result); +		setText(result_str); +		selectAll(); +	} + +	return success; +} +  void LLLineEditor::onMouseCaptureLost()  {  	endSelection(); diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h index 4b77708590..583bde360a 100644 --- a/indra/llui/lllineeditor.h +++ b/indra/llui/lllineeditor.h @@ -221,6 +221,7 @@ public:  	void			deleteSelection();  	void			setSelectAllonFocusReceived(BOOL b); +	void			setSelectAllonCommit(BOOL b) { mSelectAllonCommit = b; }  	typedef boost::function<void (LLLineEditor* caller, void* user_data)> callback_t;  	void			setKeystrokeCallback(callback_t callback, void* user_data); @@ -241,6 +242,7 @@ public:  	static BOOL		postvalidateFloat(const std::string &str);  	bool			prevalidateInput(const LLWString& wstr); +	BOOL			evaluateFloat();  	// line history support:  	void			setEnableLineHistory( BOOL enabled ) { mHaveHistory = enabled; } // switches line history on or off  @@ -340,6 +342,7 @@ protected:  	BOOL		mDrawAsterixes;  	BOOL		mSelectAllonFocusReceived; +	BOOL		mSelectAllonCommit;  	BOOL		mPassDelete;  	BOOL		mReadOnly; diff --git a/indra/llui/llspinctrl.cpp b/indra/llui/llspinctrl.cpp index 15a7438ec9..934879cdfd 100644 --- a/indra/llui/llspinctrl.cpp +++ b/indra/llui/llspinctrl.cpp @@ -44,7 +44,7 @@  #include "llresmgr.h"  #include "lluictrlfactory.h" -const U32 MAX_STRING_LENGTH = 32; +const U32 MAX_STRING_LENGTH = 255;  static LLDefaultChildRegistry::Register<LLSpinCtrl> r2("spinner"); @@ -124,14 +124,7 @@ LLSpinCtrl::LLSpinCtrl(const LLSpinCtrl::Params& p)  	params.max_length.bytes(MAX_STRING_LENGTH);  	params.commit_callback.function((boost::bind(&LLSpinCtrl::onEditorCommit, this, _2))); -	if( mPrecision>0 )//should accept float numbers -	{ -		params.prevalidate_callback(&LLTextValidate::validateFloat); -	} -	else //should accept int numbers -	{ -		params.prevalidate_callback(&LLTextValidate::validateInt); -	} +	//*NOTE: allow entering of any chars for LLCalc, proper input will be evaluated on commit  	params.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM);  	mEditor = LLUICtrlFactory::create<LLLineEditor> (params); @@ -140,6 +133,7 @@ LLSpinCtrl::LLSpinCtrl(const LLSpinCtrl::Params& p)  	// than when it doesn't.  Instead, if you always have to double click to select all the text,   	// it's easier to understand  	//mEditor->setSelectAllonFocusReceived(TRUE); +	mEditor->setSelectAllonCommit(FALSE);  	addChild(mEditor);  	updateEditor(); @@ -304,9 +298,10 @@ void LLSpinCtrl::onEditorCommit( const LLSD& data )  {  	BOOL success = FALSE; -	std::string text = mEditor->getText(); -	if( LLLineEditor::postvalidateFloat( text ) ) +	if( mEditor->evaluateFloat() )  	{ +		std::string text = mEditor->getText(); +  		LLLocale locale(LLLocale::USER_LOCALE);  		F32 val = (F32) atof(text.c_str()); @@ -327,7 +322,11 @@ void LLSpinCtrl::onEditorCommit( const LLSD& data )  	}  	updateEditor(); -	if( !success ) +	if( success ) +	{ +		updateEditor(); +	} +	else  	{  		reportInvalidData();		  	} diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 8954937f69..492cfe7c1b 100644..100755 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -577,7 +577,10 @@ void LLAgent::setFlying(BOOL fly)  // static  void LLAgent::toggleFlying()  { -	LLToolPie::instance().stopClickToWalk(); +	if ( gAgent.mAutoPilot ) +	{ +		LLToolPie::instance().stopClickToWalk(); +	}  	BOOL fly = !gAgent.getFlying(); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 1d9519d675..80ac385e3b 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -56,6 +56,7 @@  #include "llallocator.h"  #include "llares.h"   #include "llcurl.h" +#include "llcalc.h"  #include "lltexturestats.h"  #include "lltexturestats.h"  #include "llviewerwindow.h" @@ -1543,7 +1544,9 @@ bool LLAppViewer::cleanup()  	// Note: this is where gLocalSpeakerMgr and gActiveSpeakerMgr used to be deleted.  	LLWorldMap::getInstance()->reset(); // release any images -	 + +	LLCalc::cleanUp(); +  	llinfos << "Global stuff deleted" << llendflush;  	if (gAudiop) diff --git a/indra/newview/llassetuploadresponders.cpp b/indra/newview/llassetuploadresponders.cpp index d7ba4ea470..5b9a449be1 100644 --- a/indra/newview/llassetuploadresponders.cpp +++ b/indra/newview/llassetuploadresponders.cpp @@ -449,7 +449,7 @@ void LLSendTexLayerResponder::uploadComplete(const LLSD& content)  	std::string result = content["state"];  	LLUUID new_id = content["new_asset"]; -	llinfos << "result: " << result << "new_id:" << new_id << llendl; +	llinfos << "result: " << result << " new_id: " << new_id << llendl;  	if (result == "complete"  		&& mBakedUploadData != NULL)  	{	// Invoke  diff --git a/indra/newview/llassetuploadresponders.h b/indra/newview/llassetuploadresponders.h index 70871b62e2..381b919c4a 100644 --- a/indra/newview/llassetuploadresponders.h +++ b/indra/newview/llassetuploadresponders.h @@ -112,6 +112,7 @@ private:  struct LLBakedUploadData;  class LLSendTexLayerResponder : public LLAssetUploadResponder  { +	LOG_CLASS(LLSendTexLayerResponder);  public:  	LLSendTexLayerResponder(const LLSD& post_data,  							const LLUUID& vfile_id, diff --git a/indra/newview/llpanelface.cpp b/indra/newview/llpanelface.cpp index 07c7f35989..a4f6921f98 100644 --- a/indra/newview/llpanelface.cpp +++ b/indra/newview/llpanelface.cpp @@ -30,6 +30,7 @@  #include "llpanelface.h"  // library includes +#include "llcalc.h"  #include "llerror.h"  #include "llfocusmgr.h"  #include "llrect.h" @@ -926,6 +927,16 @@ void LLPanelFace::getState()  				getChildView("button apply")->setEnabled(enabled);  			}  		} + +		// Set variable values for numeric expressions +		LLCalc* calcp = LLCalc::getInstance(); +		calcp->setVar(LLCalc::TEX_U_SCALE, childGetValue("TexScaleU").asReal()); +		calcp->setVar(LLCalc::TEX_V_SCALE, childGetValue("TexScaleV").asReal()); +		calcp->setVar(LLCalc::TEX_U_OFFSET, childGetValue("TexOffsetU").asReal()); +		calcp->setVar(LLCalc::TEX_V_OFFSET, childGetValue("TexOffsetV").asReal()); +		calcp->setVar(LLCalc::TEX_ROTATION, childGetValue("TexRot").asReal()); +		calcp->setVar(LLCalc::TEX_TRANSPARENCY, childGetValue("ColorTrans").asReal()); +		calcp->setVar(LLCalc::TEX_GLOW, childGetValue("glow").asReal());  	}  	else  	{ @@ -961,6 +972,16 @@ void LLPanelFace::getState()  		//getChildView("has media")->setEnabled(FALSE);  		//getChildView("media info set")->setEnabled(FALSE); + +		// Set variable values for numeric expressions +		LLCalc* calcp = LLCalc::getInstance(); +		calcp->clearVar(LLCalc::TEX_U_SCALE); +		calcp->clearVar(LLCalc::TEX_V_SCALE); +		calcp->clearVar(LLCalc::TEX_U_OFFSET); +		calcp->clearVar(LLCalc::TEX_V_OFFSET); +		calcp->clearVar(LLCalc::TEX_ROTATION); +		calcp->clearVar(LLCalc::TEX_TRANSPARENCY); +		calcp->clearVar(LLCalc::TEX_GLOW);		  	}  } diff --git a/indra/newview/llpanelobject.cpp b/indra/newview/llpanelobject.cpp index 52917ff20b..c222bbb191 100644 --- a/indra/newview/llpanelobject.cpp +++ b/indra/newview/llpanelobject.cpp @@ -41,6 +41,7 @@  // project includes  #include "llagent.h"  #include "llbutton.h" +#include "llcalc.h"  #include "llcheckboxctrl.h"  #include "llcolorswatch.h"  #include "llcombobox.h" @@ -318,6 +319,8 @@ void LLPanelObject::getState( )  		}  	} +	LLCalc* calcp = LLCalc::getInstance(); +  	LLVOVolume *volobjp = NULL;  	if ( objectp && (objectp->getPCode() == LL_PCODE_VOLUME))  	{ @@ -334,6 +337,7 @@ void LLPanelObject::getState( )  		// Disable all text input fields  		clearCtrls(); +		calcp->clearAllVariables();  		return;  	} @@ -360,12 +364,18 @@ void LLPanelObject::getState( )  		mCtrlPosX->set( vec.mV[VX] );  		mCtrlPosY->set( vec.mV[VY] );  		mCtrlPosZ->set( vec.mV[VZ] ); +		calcp->setVar(LLCalc::X_POS, vec.mV[VX]); +		calcp->setVar(LLCalc::Y_POS, vec.mV[VY]); +		calcp->setVar(LLCalc::Z_POS, vec.mV[VZ]);  	}  	else  	{  		mCtrlPosX->clear();  		mCtrlPosY->clear();  		mCtrlPosZ->clear(); +		calcp->clearVar(LLCalc::X_POS); +		calcp->clearVar(LLCalc::Y_POS); +		calcp->clearVar(LLCalc::Z_POS);  	} @@ -380,12 +390,18 @@ void LLPanelObject::getState( )  		mCtrlScaleX->set( vec.mV[VX] );  		mCtrlScaleY->set( vec.mV[VY] );  		mCtrlScaleZ->set( vec.mV[VZ] ); +		calcp->setVar(LLCalc::X_SCALE, vec.mV[VX]); +		calcp->setVar(LLCalc::Y_SCALE, vec.mV[VY]); +		calcp->setVar(LLCalc::Z_SCALE, vec.mV[VZ]);  	}  	else  	{  		mCtrlScaleX->clear();  		mCtrlScaleY->clear();  		mCtrlScaleZ->clear(); +		calcp->setVar(LLCalc::X_SCALE, 0.f); +		calcp->setVar(LLCalc::Y_SCALE, 0.f); +		calcp->setVar(LLCalc::Z_SCALE, 0.f);  	}  	mLabelSize->setEnabled( enable_scale ); @@ -405,12 +421,18 @@ void LLPanelObject::getState( )  		mCtrlRotX->set( mCurEulerDegrees.mV[VX] );  		mCtrlRotY->set( mCurEulerDegrees.mV[VY] );  		mCtrlRotZ->set( mCurEulerDegrees.mV[VZ] ); +		calcp->setVar(LLCalc::X_ROT, mCurEulerDegrees.mV[VX]); +		calcp->setVar(LLCalc::Y_ROT, mCurEulerDegrees.mV[VY]); +		calcp->setVar(LLCalc::Z_ROT, mCurEulerDegrees.mV[VZ]);  	}  	else  	{  		mCtrlRotX->clear();  		mCtrlRotY->clear();  		mCtrlRotZ->clear(); +		calcp->clearVar(LLCalc::X_ROT); +		calcp->clearVar(LLCalc::Y_ROT); +		calcp->clearVar(LLCalc::Z_ROT);  	}  	mLabelRotation->setEnabled( enable_rotate ); @@ -625,9 +647,9 @@ void LLPanelObject::getState( )  		F32 end_t	= volume_params.getEndT();  		// Hollowness -		F32 hollow = volume_params.getHollow(); -		mSpinHollow->set( 100.f * hollow ); - +		F32 hollow = 100.f * volume_params.getHollow(); +		mSpinHollow->set( hollow ); +		calcp->setVar(LLCalc::HOLLOW, hollow);  		// All hollow objects allow a shape to be selected.  		if (hollow > 0.f)  		{ @@ -679,6 +701,10 @@ void LLPanelObject::getState( )  		mSpinCutEnd		->set( cut_end );  		mCtrlPathBegin	->set( adv_cut_begin );  		mCtrlPathEnd	->set( adv_cut_end ); +		calcp->setVar(LLCalc::CUT_BEGIN, cut_begin); +		calcp->setVar(LLCalc::CUT_END, cut_end); +		calcp->setVar(LLCalc::PATH_BEGIN, adv_cut_begin); +		calcp->setVar(LLCalc::PATH_END, adv_cut_end);  		// Twist  		F32 twist		= volume_params.getTwist(); @@ -697,18 +723,24 @@ void LLPanelObject::getState( )  		mSpinTwist		->set( twist );  		mSpinTwistBegin	->set( twist_begin ); +		calcp->setVar(LLCalc::TWIST_END, twist); +		calcp->setVar(LLCalc::TWIST_BEGIN, twist_begin);  		// Shear  		F32 shear_x = volume_params.getShearX();  		F32 shear_y = volume_params.getShearY();  		mSpinShearX->set( shear_x );  		mSpinShearY->set( shear_y ); +		calcp->setVar(LLCalc::X_SHEAR, shear_x); +		calcp->setVar(LLCalc::Y_SHEAR, shear_y);  		// Taper  		F32 taper_x	= volume_params.getTaperX();  		F32 taper_y = volume_params.getTaperY();  		mSpinTaperX->set( taper_x );  		mSpinTaperY->set( taper_y ); +		calcp->setVar(LLCalc::X_TAPER, taper_x); +		calcp->setVar(LLCalc::Y_TAPER, taper_y);  		// Radius offset.  		F32 radius_offset = volume_params.getRadiusOffset(); @@ -738,10 +770,12 @@ void LLPanelObject::getState( )  			}  		}  		mSpinRadiusOffset->set( radius_offset); +		calcp->setVar(LLCalc::RADIUS_OFFSET, radius_offset);  		// Revolutions  		F32 revolutions = volume_params.getRevolutions();  		mSpinRevolutions->set( revolutions ); +		calcp->setVar(LLCalc::REVOLUTIONS, revolutions);  		// Skew  		F32 skew	= volume_params.getSkew(); @@ -766,6 +800,7 @@ void LLPanelObject::getState( )  			}  		}  		mSpinSkew->set( skew ); +		calcp->setVar(LLCalc::SKEW, skew);  	}  	// Compute control visibility, label names, and twist range. @@ -869,6 +904,8 @@ void LLPanelObject::getState( )  	case MI_RING:  		mSpinScaleX->set( scale_x );  		mSpinScaleY->set( scale_y ); +		calcp->setVar(LLCalc::X_HOLE, scale_x); +		calcp->setVar(LLCalc::Y_HOLE, scale_y);  		mSpinScaleX->setMinValue(OBJECT_MIN_HOLE_SIZE);  		mSpinScaleX->setMaxValue(OBJECT_MAX_HOLE_SIZE_X);  		mSpinScaleY->setMinValue(OBJECT_MIN_HOLE_SIZE); @@ -883,6 +920,14 @@ void LLPanelObject::getState( )  			mSpinScaleX->setMaxValue(1.f);  			mSpinScaleY->setMinValue(-1.f);  			mSpinScaleY->setMaxValue(1.f); + +			// Torus' Hole Size is Box/Cyl/Prism's Taper +			calcp->setVar(LLCalc::X_TAPER, 1.f - scale_x); +			calcp->setVar(LLCalc::Y_TAPER, 1.f - scale_y); + +			// Box/Cyl/Prism have no hole size +			calcp->setVar(LLCalc::X_HOLE, 0.f); +			calcp->setVar(LLCalc::Y_HOLE, 0.f);  		}  		break;  	} diff --git a/indra/newview/llpanelwearing.cpp b/indra/newview/llpanelwearing.cpp index 0645fd8a54..f19b54c1d4 100644 --- a/indra/newview/llpanelwearing.cpp +++ b/indra/newview/llpanelwearing.cpp @@ -38,6 +38,8 @@  #include "llsidetray.h"  #include "llviewermenu.h"  #include "llwearableitemslist.h" +#include "llsdserialize.h" +#include "llclipboard.h"  // Context menu and Gear menu helper.  static void edit_outfit() @@ -58,6 +60,7 @@ public:  		registrar.add("Gear.Edit", boost::bind(&edit_outfit));  		registrar.add("Gear.TakeOff", boost::bind(&LLWearingGearMenu::onTakeOff, this)); +		registrar.add("Gear.Copy", boost::bind(&LLPanelWearing::copyToClipboard, mPanelWearing));  		enable_registrar.add("Gear.OnEnable", boost::bind(&LLPanelWearing::isActionEnabled, mPanelWearing, _2)); @@ -280,4 +283,25 @@ void LLPanelWearing::getSelectedItemsUUIDs(uuid_vec_t& selected_uuids) const  	mCOFItemsList->getSelectedUUIDs(selected_uuids);  } +void LLPanelWearing::copyToClipboard() +{ +	std::string text; +	std::vector<LLSD> data; +	mCOFItemsList->getValues(data); + +	for(std::vector<LLSD>::const_iterator iter = data.begin(); iter != data.end();) +	{ +		LLSD uuid = (*iter); +		LLViewerInventoryItem* item = gInventory.getItem(uuid); + +		iter++; +		if (item != NULL) +		{ +			// Append a newline to all but the last line +			text += iter != data.end() ? item->getName() + "\n" : item->getName(); +		} +	} + +	gClipboard.copyFromString(utf8str_to_wstring(text)); +}  // EOF diff --git a/indra/newview/llpanelwearing.h b/indra/newview/llpanelwearing.h index 157b2c4c5f..9a212b3cca 100644 --- a/indra/newview/llpanelwearing.h +++ b/indra/newview/llpanelwearing.h @@ -60,6 +60,8 @@ public:  	/*virtual*/ void getSelectedItemsUUIDs(uuid_vec_t& selected_uuids) const; +	/*virtual*/ void copyToClipboard(); +  	boost::signals2::connection setSelectionChangeCallback(commit_callback_t cb);  	bool hasItemSelected(); diff --git a/indra/newview/lltexlayer.cpp b/indra/newview/lltexlayer.cpp index 500c2a7b86..bd41aa64f0 100644 --- a/indra/newview/lltexlayer.cpp +++ b/indra/newview/lltexlayer.cpp @@ -51,6 +51,9 @@  using namespace LLVOAvatarDefines; +static const S32 BAKE_UPLOAD_ATTEMPTS = 7; +static const F32 BAKE_UPLOAD_RETRY_DELAY = 2.f; // actual delay grows by power of 2 each attempt +  class LLTexLayerInfo  {  	friend class LLTexLayer; @@ -93,11 +96,13 @@ private:  //-----------------------------------------------------------------------------  LLBakedUploadData::LLBakedUploadData(const LLVOAvatarSelf* avatar,  									 LLTexLayerSet* layerset, -									 const LLUUID& id) :  +									 const LLUUID& id, +									 bool highest_res) :  	mAvatar(avatar),  	mTexLayerSet(layerset),  	mID(id), -	mStartTime(LLFrameTimer::getTotalTime())		// Record starting time +	mStartTime(LLFrameTimer::getTotalTime()),		// Record starting time +	mIsHighestRes(highest_res)  {   } @@ -116,6 +121,7 @@ LLTexLayerSetBuffer::LLTexLayerSetBuffer(LLTexLayerSet* const owner,  	mUploadPending(FALSE), // Not used for any logic here, just to sync sending of updates  	mNeedsUpload(FALSE),  	mNumLowresUploads(0), +	mUploadFailCount(0),  	mNeedsUpdate(TRUE),  	mNumLowresUpdates(0),  	mTexLayerSet(owner) @@ -204,6 +210,7 @@ void LLTexLayerSetBuffer::cancelUpload()  	mNeedsUpload = FALSE;  	mUploadPending = FALSE;  	mNeedsUploadTimer.pause(); +	mUploadRetryTimer.reset();  }  void LLTexLayerSetBuffer::pushProjection() const @@ -356,25 +363,38 @@ BOOL LLTexLayerSetBuffer::isReadyToUpload() const  	if (!gAgentQueryManager.hasNoPendingQueries()) return FALSE; // Can't upload if there are pending queries.  	if (isAgentAvatarValid() && !gAgentAvatarp->isUsingBakedTextures()) return FALSE; // Don't upload if avatar is using composites. -	// If we requested an upload and have the final LOD ready, then upload. -	if (mTexLayerSet->isLocalTextureDataFinal()) return TRUE; - -	// Upload if we've hit a timeout.  Upload is a pretty expensive process so we need to make sure -	// we aren't doing uploads too frequently. -	const U32 texture_timeout = gSavedSettings.getU32("AvatarBakedTextureUploadTimeout"); -	if (texture_timeout != 0) +	BOOL ready = FALSE; +	if (mTexLayerSet->isLocalTextureDataFinal()) +	{ +		// If we requested an upload and have the final LOD ready, upload (or wait a while if this is a retry) +		if (mUploadFailCount == 0) +		{ +			ready = TRUE; +		} +		else +		{ +			ready = mUploadRetryTimer.getElapsedTimeF32() >= BAKE_UPLOAD_RETRY_DELAY * (1 << (mUploadFailCount - 1)); +		} +	} +	else  	{ -		// The timeout period increases exponentially between every lowres upload in order to prevent -		// spamming the server with frequent uploads. -		const U32 texture_timeout_threshold = texture_timeout*(1 << mNumLowresUploads); +		// Upload if we've hit a timeout.  Upload is a pretty expensive process so we need to make sure +		// we aren't doing uploads too frequently. +		const U32 texture_timeout = gSavedSettings.getU32("AvatarBakedTextureUploadTimeout"); +		if (texture_timeout != 0) +		{ +			// The timeout period increases exponentially between every lowres upload in order to prevent +			// spamming the server with frequent uploads. +			const U32 texture_timeout_threshold = texture_timeout*(1 << mNumLowresUploads); -		// If we hit our timeout and have textures available at even lower resolution, then upload. -		const BOOL is_upload_textures_timeout = mNeedsUploadTimer.getElapsedTimeF32() >= texture_timeout_threshold; -		const BOOL has_lower_lod = mTexLayerSet->isLocalTextureDataAvailable(); -		if (has_lower_lod && is_upload_textures_timeout) return TRUE;  +			// If we hit our timeout and have textures available at even lower resolution, then upload. +			const BOOL is_upload_textures_timeout = mNeedsUploadTimer.getElapsedTimeF32() >= texture_timeout_threshold; +			const BOOL has_lower_lod = mTexLayerSet->isLocalTextureDataAvailable(); +			ready = has_lower_lod && is_upload_textures_timeout; +		}  	} -	return FALSE; +	return ready;  }  BOOL LLTexLayerSetBuffer::isReadyToUpdate() const @@ -482,17 +502,20 @@ void LLTexLayerSetBuffer::doUpload()  			if (valid)  			{ +				const bool highest_lod = mTexLayerSet->isLocalTextureDataFinal();  				// Baked_upload_data is owned by the responder and deleted after the request completes.  				LLBakedUploadData* baked_upload_data = new LLBakedUploadData(gAgentAvatarp,   																			 this->mTexLayerSet,  -																			 asset_id); +																			 asset_id, +																			 highest_lod);  				// upload ID is used to avoid overlaps, e.g. when the user rapidly makes two changes outside of Face Edit.  				mUploadID = asset_id;  				// Upload the image  				const std::string url = gAgent.getRegion()->getCapability("UploadBakedTexture");  				if(!url.empty() -					&& !LLPipeline::sForceOldBakedUpload) // toggle debug setting UploadBakedTexOld to change between the new caps method and old method +					&& !LLPipeline::sForceOldBakedUpload // toggle debug setting UploadBakedTexOld to change between the new caps method and old method +					&& (mUploadFailCount < (BAKE_UPLOAD_ATTEMPTS - 1))) // Try last ditch attempt via asset store if cap upload is failing.  				{  					LLSD body = LLSD::emptyMap();  					// The responder will call LLTexLayerSetBuffer::onTextureUploadComplete() @@ -511,7 +534,6 @@ void LLTexLayerSetBuffer::doUpload()  					llinfos << "Baked texture upload via Asset Store." <<  llendl;  				} -				const BOOL highest_lod = mTexLayerSet->isLocalTextureDataFinal();	  				if (highest_lod)  				{  					// Sending the final LOD for the baked texture.  All done, pause  @@ -603,14 +625,15 @@ void LLTexLayerSetBuffer::onTextureUploadComplete(const LLUUID& uuid,  {  	LLBakedUploadData* baked_upload_data = (LLBakedUploadData*)userdata; -	if ((result == 0) && -		isAgentAvatarValid() && +	if (isAgentAvatarValid() &&  		!gAgentAvatarp->isDead() &&  		(baked_upload_data->mAvatar == gAgentAvatarp) && // Sanity check: only the user's avatar should be uploading textures.  		(baked_upload_data->mTexLayerSet->hasComposite()))  	{  		LLTexLayerSetBuffer* layerset_buffer = baked_upload_data->mTexLayerSet->getComposite(); -			 +		S32 failures = layerset_buffer->mUploadFailCount; +		layerset_buffer->mUploadFailCount = 0; +  		if (layerset_buffer->mUploadID.isNull())  		{  			// The upload got canceled, we should be in the @@ -626,20 +649,28 @@ void LLTexLayerSetBuffer::onTextureUploadComplete(const LLUUID& uuid,  		{  			// This is the upload we're currently waiting for.  			layerset_buffer->mUploadID.setNull(); +			const std::string name(baked_upload_data->mTexLayerSet->getBodyRegionName()); +			const std::string resolution = baked_upload_data->mIsHighestRes ? " full res " : " low res ";  			if (result >= 0)  			{ -				layerset_buffer->mUploadPending = FALSE; +				layerset_buffer->mUploadPending = FALSE; // Allows sending of AgentSetAppearance later  				LLVOAvatarDefines::ETextureIndex baked_te = gAgentAvatarp->getBakedTE(layerset_buffer->mTexLayerSet);  				// Update baked texture info with the new UUID  				U64 now = LLFrameTimer::getTotalTime();		// Record starting time -				llinfos << "Baked texture upload took " << (S32)((now - baked_upload_data->mStartTime) / 1000) << " ms" << llendl; +				llinfos << "Baked" << resolution << "texture upload for " << name << " took " << (S32)((now - baked_upload_data->mStartTime) / 1000) << " ms" << llendl;  				gAgentAvatarp->setNewBakedTexture(baked_te, uuid);  			}  			else  			{	 -				// Avatar appearance is changing, ignore the upload results -				llinfos << "Baked upload failed. Reason: " << result << llendl; -				// *FIX: retry upload after n seconds, asset server could be busy +				++failures; +				S32 max_attempts = baked_upload_data->mIsHighestRes ? BAKE_UPLOAD_ATTEMPTS : 1; // only retry final bakes +				llwarns << "Baked" << resolution << "texture upload for " << name << " failed (attempt " << failures << "/" << max_attempts << ")" << llendl; +				if (failures < max_attempts) +				{ +					layerset_buffer->mUploadFailCount = failures; +					layerset_buffer->mUploadRetryTimer.start(); +					layerset_buffer->requestUpload(); +				}  			}  		}  		else diff --git a/indra/newview/lltexlayer.h b/indra/newview/lltexlayer.h index 2d710d2dce..85dadb213c 100644 --- a/indra/newview/lltexlayer.h +++ b/indra/newview/lltexlayer.h @@ -312,6 +312,8 @@ private:  	BOOL					mUploadPending; 				// Whether we have received back the new baked textures  	LLUUID					mUploadID; 						// The current upload process (null if none).  	LLFrameTimer    		mNeedsUploadTimer; 				// Tracks time since upload was requested and performed. +	S32						mUploadFailCount;				// Number of consecutive upload failures +	LLFrameTimer    		mUploadRetryTimer; 				// Tracks time since last upload failure.  	//--------------------------------------------------------------------  	// Updates @@ -363,12 +365,14 @@ struct LLBakedUploadData  {  	LLBakedUploadData(const LLVOAvatarSelf* avatar,   					  LLTexLayerSet* layerset,  -					  const LLUUID& id); +					  const LLUUID& id, +					  bool highest_res);  	~LLBakedUploadData() {}  	const LLUUID				mID;  	const LLVOAvatarSelf*		mAvatar; // note: backlink only; don't LLPointer   	LLTexLayerSet*				mTexLayerSet;     	const U64					mStartTime;	// for measuring baked texture upload time +   	const bool					mIsHighestRes; // whether this is a "final" bake, or intermediate low res  };  #endif  // LL_LLTEXLAYER_H diff --git a/indra/newview/skins/default/xui/en/menu_wearing_gear.xml b/indra/newview/skins/default/xui/en/menu_wearing_gear.xml index 0ac2c14253..0e858ccf10 100644 --- a/indra/newview/skins/default/xui/en/menu_wearing_gear.xml +++ b/indra/newview/skins/default/xui/en/menu_wearing_gear.xml @@ -20,4 +20,11 @@           function="Gear.OnEnable"           parameter="take_off" />      </menu_item_call> +    <menu_item_call +     label="Copy outfit list to clipboard" +     layout="topleft" +     name="copy"> +        <on_click +         function="Gear.Copy" /> +    </menu_item_call>  </toggleable_menu> | 
