diff --git a/.gitignore b/.gitignore index 7273210c5..c56bf2a54 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /installed.xml +/indra/llcommon/llversionviewer.h /indra/build-* /indra/tools/vstool/obj/ *.aps @@ -11,11 +12,13 @@ /indra/viewer-* /indra/newview/vivox-runtime/ /libraries/ +/lib/ *.pyc *.orig *.rej *.bak *~ +*.DS_Store /LICENSES/ /edited-files.txt qtcreator-build/ diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index 11d3f7ab7..09eade34f 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -26,7 +26,7 @@ set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") include(Variables) # Load versions now. Install locations need them. -include(Versions) +include(BuildVersion) include(UnixInstall) diff --git a/indra/cmake/00-Common.cmake b/indra/cmake/00-Common.cmake index fa0871ddc..87f8da720 100644 --- a/indra/cmake/00-Common.cmake +++ b/indra/cmake/00-Common.cmake @@ -28,6 +28,9 @@ set(CMAKE_CONFIGURATION_TYPES "RelWithDebInfo;Release;ReleaseSSE2;Debug" CACHE S # Platform-specific compilation flags. if (WINDOWS) + # Remove default /Zm1000 flag that cmake inserts + string (REPLACE "/Zm1000" " " CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) + # Don't build DLLs. set(BUILD_SHARED_LIBS OFF) @@ -67,6 +70,10 @@ if (WINDOWS) /Oy- ) + # configure win32 API for windows XP+ compatibility + set(WINVER "0x0501" CACHE STRING "Win32 API Target version (see http://msdn.microsoft.com/en-us/library/aa383745%28v=VS.85%29.aspx)") + add_definitions("/DWINVER=${WINVER}" "/D_WIN32_WINNT=${WINVER}") + if(MSVC80 OR MSVC90 OR MSVC10) set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -D_SECURE_STL=0 -D_HAS_ITERATOR_DEBUGGING=0" @@ -113,7 +120,7 @@ if (WINDOWS) endif (WINDOWS) -set (GCC_EXTRA_OPTIMIZATIONS "-ffast-math -frounding-math") +set (GCC_EXTRA_OPTIMIZATIONS "-ffast-math") if (LINUX) set(CMAKE_SKIP_RPATH TRUE) @@ -155,10 +162,10 @@ if (LINUX) add_definitions(-D_FORTIFY_SOURCE=2) endif (NOT ${GXX_VERSION} MATCHES " 4.1.*Red Hat") endif (${GXX_VERSION} STREQUAL ${CXX_VERSION}) - - #Lets actualy get a numerical version of gxx's version + + #Lets actually get a numerical version of gxx's version STRING(REGEX REPLACE ".* ([0-9])\\.([0-9])\\.([0-9]).*" "\\1\\2\\3" CXX_VERSION ${CXX_VERSION}) - + #gcc 4.3 and above don't like the LL boost if(${CXX_VERSION} GREATER 429) add_definitions(-Wno-parentheses) @@ -182,6 +189,8 @@ if (LINUX) -pthread ) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99") + add_definitions(-DAPPID=secondlife) 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. @@ -200,10 +209,10 @@ if (LINUX) if (NOT STANDALONE) set(MARCH_FLAG " -march=pentium4") endif (NOT STANDALONE) - set(CMAKE_CXX_FLAGS_RELEASESSE2 "${CMAKE_CXX_FLAGS_RELEASESSE2}${MARCH_FLAG} -mfpmath=sse -msse2 ${GCC_EXTRA_OPTIMIZATIONS}") - set(CMAKE_C_FLAGS_RELEASESSE2 "${CMAKE_C_FLAGS_RELEASESSE2}${MARCH_FLAG} -mfpmath=sse -msse2 ${GCC_EXTRA_OPTIMIZATIONS}") - set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}${MARCH_FLAG} -mfpmath=sse -msse2 ${GCC_EXTRA_OPTIMIZATIONS}") - set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}${MARCH_FLAG} -mfpmath=sse -msse2 ${GCC_EXTRA_OPTIMIZATIONS}") + set(CMAKE_CXX_FLAGS_RELEASESSE2 "${CMAKE_CXX_FLAGS_RELEASESSE2}${MARCH_FLAG} -mfpmath=sse,387 -msse2 ${GCC_EXTRA_OPTIMIZATIONS}") + set(CMAKE_C_FLAGS_RELEASESSE2 "${CMAKE_C_FLAGS_RELEASESSE2}${MARCH_FLAG} -mfpmath=sse,387 -msse2 ${GCC_EXTRA_OPTIMIZATIONS}") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}${MARCH_FLAG} -mfpmath=sse,387 -msse2 ${GCC_EXTRA_OPTIMIZATIONS}") + set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}${MARCH_FLAG} -mfpmath=sse,387 -msse2 ${GCC_EXTRA_OPTIMIZATIONS}") endif (${ARCH} STREQUAL "x86_64") set(CMAKE_CXX_FLAGS_DEBUG "-fno-inline ${CMAKE_CXX_FLAGS_DEBUG} -msse2") diff --git a/indra/cmake/APR.cmake b/indra/cmake/APR.cmake index d1f089891..5c434f4bf 100644 --- a/indra/cmake/APR.cmake +++ b/indra/cmake/APR.cmake @@ -29,12 +29,12 @@ else (STANDALONE) ) elseif (DARWIN) set(APR_LIBRARIES - debug ${ARCH_PREBUILT_DIRS_DEBUG}/libapr-1.0.3.7.dylib - optimized ${ARCH_PREBUILT_DIRS_RELEASE}/libapr-1.0.3.7.dylib + debug ${ARCH_PREBUILT_DIRS_DEBUG}/libapr-1.0.dylib + optimized ${ARCH_PREBUILT_DIRS_RELEASE}/libapr-1.0.dylib ) set(APRUTIL_LIBRARIES - debug ${ARCH_PREBUILT_DIRS_DEBUG}/libaprutil-1.0.3.8.dylib - optimized ${ARCH_PREBUILT_DIRS_RELEASE}/libaprutil-1.0.3.8.dylib + debug ${ARCH_PREBUILT_DIRS_DEBUG}/libaprutil-1.0.dylib + optimized ${ARCH_PREBUILT_DIRS_RELEASE}/libaprutil-1.0.dylib ) set(APRICONV_LIBRARIES iconv) else (WINDOWS) diff --git a/indra/cmake/Boost.cmake b/indra/cmake/Boost.cmake index 0cd10bd6c..1275d0547 100644 --- a/indra/cmake/Boost.cmake +++ b/indra/cmake/Boost.cmake @@ -10,7 +10,7 @@ if (STANDALONE) set(BOOST_FILESYSTEM_LIBRARY boost_filesystem-mt) set(BOOST_PROGRAM_OPTIONS_LIBRARY boost_program_options-mt) set(BOOST_REGEX_LIBRARY boost_regex-mt) - set(BOOST_SYSTEM_LIBRARY boost_system-mt) + set(BOOST_SYSTEM_LIBRARY boost_system-mt) else (STANDALONE) use_prebuilt_binary(boost) set(Boost_INCLUDE_DIRS ${LIBS_PREBUILT_DIR}/include) diff --git a/indra/cmake/BuildVersion.cmake b/indra/cmake/BuildVersion.cmake index 30817b950..041927f10 100644 --- a/indra/cmake/BuildVersion.cmake +++ b/indra/cmake/BuildVersion.cmake @@ -1,23 +1,45 @@ # -*- cmake -*- -function (build_version _target) - # Read version components from the header file. - file(STRINGS ${LIBS_OPEN_DIR}/llcommon/llversion${_target}.h lines - REGEX " LL_VERSION_") - foreach(line ${lines}) - string(REGEX REPLACE ".*LL_VERSION_([A-Z]+).*" "\\1" comp "${line}") - string(REGEX REPLACE ".* = ([0-9]+);.*" "\\1" value "${line}") - set(v${comp} "${value}") - endforeach(line) +# Read version components from the header file. +file(STRINGS ${LIBS_OPEN_DIR}/llcommon/llversionviewer.h.in lines + REGEX " LL_VERSION_") +foreach(line ${lines}) + string(REGEX REPLACE ".*LL_VERSION_([A-Z]+).*" "\\1" comp "${line}") + string(REGEX REPLACE ".* = ([0-9]+);.*" "\\1" value "${line}") + set(v${comp} "${value}") +endforeach(line) - # Compose the version. - set(${_target}_VERSION "${vMAJOR}.${vMINOR}.${vPATCH}.${vBUILD}") - if (${_target}_VERSION MATCHES "^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$") - message(STATUS "Version of ${_target} is ${${_target}_VERSION}") - else (${_target}_VERSION MATCHES "^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$") - message(FATAL_ERROR "Could not determine ${_target} version (${${_target}_VERSION})") - endif (${_target}_VERSION MATCHES "^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$") +execute_process( + COMMAND git rev-list HEAD + OUTPUT_VARIABLE GIT_REV_LIST_STR + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_STRIP_TRAILING_WHITESPACE +) - # Report version to caller. - set(${_target}_VERSION "${${_target}_VERSION}" PARENT_SCOPE) -endfunction (build_version) +if(GIT_REV_LIST_STR) + string(REPLACE "\n" ";" GIT_REV_LIST ${GIT_REV_LIST_STR}) +else() + string(REPLACE "\n" ";" GIT_REV_LIST "") +endif() + +if(GIT_REV_LIST) + list(LENGTH GIT_REV_LIST vBUILD) +else() + set(vBUILD 99) +endif() + +configure_file( + ${CMAKE_SOURCE_DIR}/llcommon/llversionviewer.h.in + ${CMAKE_SOURCE_DIR}/llcommon/llversionviewer.h +) + +# Compose the version. +set(viewer_VERSION "${vMAJOR}.${vMINOR}.${vPATCH}.${vBUILD}") +if (viewer_VERSION MATCHES "^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$") + message(STATUS "Version is ${viewer_VERSION}") +else (viewer_VERSION MATCHES "^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$") + message(FATAL_ERROR "Could not determine version (${viewer_VERSION})") +endif (viewer_VERSION MATCHES "^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$") + +# Report version to caller. +#set(viewer_VERSION "${viewer_VERSION}" PARENT_SCOPE) diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt index 2f30f29b6..28a3d5e4f 100644 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -79,7 +79,6 @@ set(cmake_SOURCE_FILES UI.cmake UnixInstall.cmake Variables.cmake - Versions.cmake XmlRpcEpi.cmake ZLIB.cmake ) diff --git a/indra/cmake/DirectX.cmake b/indra/cmake/DirectX.cmake index 5f538c80a..e45575595 100644 --- a/indra/cmake/DirectX.cmake +++ b/indra/cmake/DirectX.cmake @@ -3,7 +3,7 @@ if (VIEWER AND WINDOWS) find_path(DIRECTX_INCLUDE_DIR dxdiag.h "$ENV{DXSDK_DIR}/Include" - "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (June 2010)/Include" + "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (June 2010)/Include" "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (March 2009)/Include" "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (August 2008)/Include" "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (June 2008)/Include" @@ -25,7 +25,7 @@ if (VIEWER AND WINDOWS) find_path(DIRECTX_LIBRARY_DIR dxguid.lib "$ENV{DXSDK_DIR}/Lib/x86" - "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (June 2010)/Lib/x86" + "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (June 2010)/Lib/x86" "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (March 2009)/Lib/x86" "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (August 2008)/Lib/x86" "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (June 2008)/Lib/x86" diff --git a/indra/cmake/FMOD.cmake b/indra/cmake/FMOD.cmake index df9b5b18e..a2d9245ff 100644 --- a/indra/cmake/FMOD.cmake +++ b/indra/cmake/FMOD.cmake @@ -7,26 +7,13 @@ if(INSTALL_PROPRIETARY) use_prebuilt_binary(fmod) endif(INSTALL_PROPRIETARY) -find_library(FMOD_LIBRARY_RELEASE +find_library(FMOD_LIBRARY NAMES fmod fmodvc fmod-3.75 PATHS - ${ARCH_PREBUILT_DIRS_RELEASE} + optimized ${ARCH_PREBUILT_DIRS_RELEASE} + debug ${ARCH_PREBUILT_DIRS_DEBUG} ) -find_library(FMOD_LIBRARY_DEBUG - NAMES fmod fmodvc fmod-3.75 - PATHS - ${ARCH_PREBUILT_DIRS_DEBUG} - ) - -if (FMOD_LIBRARY_RELEASE AND FMOD_LIBRARY_DEBUG) - set(FMOD_LIBRARY - debug ${FMOD_LIBRARY_DEBUG} - optimized ${FMOD_LIBRARY_RELEASE}) -elseif (FMOD_LIBRARY_RELEASE) - set(FMOD_LIBRARY ${FMOD_LIBRARY_RELEASE}) -endif (FMOD_LIBRARY_RELEASE AND FMOD_LIBRARY_DEBUG) - if (NOT FMOD_LIBRARY) set(FMOD_SDK_DIR CACHE PATH "Path to the FMOD SDK.") if (FMOD_SDK_DIR) diff --git a/indra/cmake/FMODEX.cmake b/indra/cmake/FMODEX.cmake index 934b74b24..bf74341fe 100644 --- a/indra/cmake/FMODEX.cmake +++ b/indra/cmake/FMODEX.cmake @@ -7,26 +7,13 @@ if(INSTALL_PROPRIETARY) use_prebuilt_binary(fmodex) endif(INSTALL_PROPRIETARY) -find_library(FMODEX_LIBRARY_RELEASE +find_library(FMODEX_LIBRARY NAMES fmodex fmodex_vc fmodexL_vc PATHS - ${ARCH_PREBUILT_DIRS_RELEASE} + optimized ${ARCH_PREBUILT_DIRS_RELEASE} + debug ${ARCH_PREBUILT_DIRS_DEBUG} ) -find_library(FMODEX_LIBRARY_DEBUG - NAMES fmodex fmodex_vc fmodexL_vc - PATHS - ${ARCH_PREBUILT_DIRS_DEBUG} - ) - -if (FMODEX_LIBRARY_RELEASE AND FMODEX_LIBRARY_DEBUG) - set(FMODEX_LIBRARY - debug ${FMODEX_LIBRARY_DEBUG} - optimized ${FMODEX_LIBRARY_RELEASE}) -elseif (FMODEX_LIBRARY_RELEASE) - set(FMODEX_LIBRARY ${FMODEX_LIBRARY_RELEASE}) -endif (FMODEX_LIBRARY_RELEASE AND FMODEX_LIBRARY_DEBUG) - if (NOT FMODEX_LIBRARY) set(FMODEX_SDK_DIR CACHE PATH "Path to the FMOD Ex SDK.") if (FMODEX_SDK_DIR) diff --git a/indra/cmake/FindJsonCpp.cmake b/indra/cmake/FindJsonCpp.cmake index cf84b309c..7dfc2b097 100644 --- a/indra/cmake/FindJsonCpp.cmake +++ b/indra/cmake/FindJsonCpp.cmake @@ -9,7 +9,7 @@ # also defined, but not for general use are # JSONCPP_LIBRARY, where to find the jsoncpp library. -FIND_PATH(JSONCPP_INCLUDE_DIR jsoncpp/json.h +FIND_PATH(JSONCPP_INCLUDE_DIR json/json.h /usr/local/include /usr/include ) diff --git a/indra/cmake/LLCommon.cmake b/indra/cmake/LLCommon.cmake index 58316a489..b1bb1b6a9 100644 --- a/indra/cmake/LLCommon.cmake +++ b/indra/cmake/LLCommon.cmake @@ -14,6 +14,7 @@ endif (DARWIN) set(LLCOMMON_INCLUDE_DIRS ${LIBS_OPEN_DIR}/cwdebug ${LIBS_OPEN_DIR}/llcommon + ${APRUTIL_INCLUDE_DIR} ${APR_INCLUDE_DIR} ${Boost_INCLUDE_DIRS} ) diff --git a/indra/cmake/LLPlugin.cmake b/indra/cmake/LLPlugin.cmake index 7ee404b9b..399cb332d 100644 --- a/indra/cmake/LLPlugin.cmake +++ b/indra/cmake/LLPlugin.cmake @@ -8,7 +8,7 @@ set(LLPLUGIN_INCLUDE_DIRS if (LINUX) # In order to support using ld.gold on linux, we need to explicitely # specify all libraries that llplugin uses. - set(LLPLUGIN_LIBRARIES llplugin pthread) + set(LLPLUGIN_LIBRARIES llplugin pthread) else (LINUX) - set(LLPLUGIN_LIBRARIES llplugin) + set(LLPLUGIN_LIBRARIES llplugin) endif (LINUX) diff --git a/indra/cmake/Python.cmake b/indra/cmake/Python.cmake index a84ab64aa..748c8c2be 100644 --- a/indra/cmake/Python.cmake +++ b/indra/cmake/Python.cmake @@ -19,7 +19,6 @@ if (WINDOWS) [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\2.5\\InstallPath] [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\2.4\\InstallPath] [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\2.3\\InstallPath] - ) elseif (EXISTS /etc/debian_version) # On Debian and Ubuntu, avoid Python 2.4 if possible. diff --git a/indra/cmake/UI.cmake b/indra/cmake/UI.cmake index 64fa85ff2..6cf3a43d2 100644 --- a/indra/cmake/UI.cmake +++ b/indra/cmake/UI.cmake @@ -38,6 +38,7 @@ else (STANDALONE) if (LINUX) set(UI_LIBRARIES atk-1.0 + X11 gdk-x11-2.0 gdk_pixbuf-2.0 Xinerama diff --git a/indra/cmake/Versions.cmake b/indra/cmake/Versions.cmake deleted file mode 100644 index c5d925e98..000000000 --- a/indra/cmake/Versions.cmake +++ /dev/null @@ -1,9 +0,0 @@ -include(BuildVersion) - -if(VIEWER) - build_version(viewer) -endif(VIEWER) - -if(SERVER) - build_version(server) -endif(SERVER) diff --git a/indra/cmake/WebKitLibPlugin.cmake b/indra/cmake/WebKitLibPlugin.cmake index 9b0635e16..7ed824aea 100644 --- a/indra/cmake/WebKitLibPlugin.cmake +++ b/indra/cmake/WebKitLibPlugin.cmake @@ -67,11 +67,16 @@ elseif (LINUX) QtNetwork QtGui QtCore + crypto + ssl +# qgif +# qjpeg jscore jpeg fontconfig X11 Xrender + Xext GL ) endif (STANDALONE) diff --git a/indra/cwdebug/debug.cc b/indra/cwdebug/debug.cc index 48392beae..2a4f1c4fa 100644 --- a/indra/cwdebug/debug.cc +++ b/indra/cwdebug/debug.cc @@ -171,7 +171,8 @@ void stop_recording_backtraces(void) channel_ct gtk DDCN("GTK"); //!< This debug channel is used for output related to gtk. channel_ct sdl DDCN("SDL"); //!< This debug channel is used for output related to sdl locking. channel_ct backtrace DDCN("BACKTRACE"); //!< This debug channel is used for backtraces. - channel_ct statemachine DDCN("STATEMACHINE"); //!< This debug channel is used class AIStateMachine. + channel_ct statemachine DDCN("STATEMACHINE"); //!< This debug channel is used for output related to class AIStateMachine. + channel_ct caps DDCN("CAPS"); //!< This debug channel is used for output related to Capabilities. } // namespace dc } // namespace DEBUGCHANNELS diff --git a/indra/cwdebug/debug.h b/indra/cwdebug/debug.h index e91eab92e..786b0c52a 100644 --- a/indra/cwdebug/debug.h +++ b/indra/cwdebug/debug.h @@ -117,6 +117,7 @@ extern CWD_API channel_ct gtk; extern CWD_API channel_ct sdl; extern CWD_API channel_ct backtrace; extern CWD_API channel_ct statemachine; +extern CWD_API channel_ct caps; #endif diff --git a/indra/llaudio/llaudioengine.h b/indra/llaudio/llaudioengine.h index 7f07a5bc2..0edaff884 100644 --- a/indra/llaudio/llaudioengine.h +++ b/indra/llaudio/llaudioengine.h @@ -239,12 +239,15 @@ protected: // A list of all audio sources that are known to the viewer at this time. // This is most likely a superset of the ones that we actually have audio // data for, or are playing back. +public://Jay: IDGAF typedef std::map source_map; +protected: typedef std::map data_map; - + +public://Jay: IDGAF source_map mAllSources; +protected: data_map mAllData; - LLAudioChannel *mChannels[MAX_CHANNELS]; // Buffers needs to change into a different data structure, as the number of buffers @@ -351,6 +354,9 @@ public: protected: LLUUID mID; // The ID of the source is that of the object if it's attached to an object. LLUUID mOwnerID; // owner of the object playing the sound +public: + const LLUUID &getOwnerID() { return mOwnerID; } +protected: F32 mPriority; F32 mGain; bool mSourceMuted; diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 5450cc386..2ff8856ec 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -29,14 +29,21 @@ set(llcommon_SOURCE_FILES llbase64.cpp llcommon.cpp llcommonutils.cpp + llcoros.cpp llcrc.cpp llcriticaldamp.cpp llcursortypes.cpp lldate.cpp + lldependencies.cpp lldictionary.cpp llerror.cpp llerrorthread.cpp llevent.cpp + lleventapi.cpp + lleventcoro.cpp + lleventdispatcher.cpp + lleventfilter.cpp + llevents.cpp lleventtimer.cpp llfasttimer_class.cpp llfile.cpp @@ -84,6 +91,7 @@ set(llcommon_SOURCE_FILES lluri.cpp lluuid.cpp llworkerthread.cpp + ll_template_cast.h metaclass.cpp metaproperty.cpp reflective.cpp @@ -121,6 +129,7 @@ set(llcommon_HEADER_FILES llclickaction.h llcommon.h llcommonutils.h + llcoros.h llcrc.h llcriticaldamp.h llcursortypes.h @@ -128,6 +137,7 @@ set(llcommon_HEADER_FILES lldarrayptr.h lldate.h lldefs.h + lldependencies.h lldeleteutils.h lldepthstack.h lldictionary.h @@ -140,6 +150,11 @@ set(llcommon_HEADER_FILES llerrorlegacy.h llerrorthread.h llevent.h + lleventapi.h + lleventcoro.h + lleventdispatcher.h + lleventfilter.h + llevents.h lleventemitter.h llextendedstatus.h lleventtimer.h @@ -213,8 +228,7 @@ set(llcommon_HEADER_FILES lluri.h lluuid.h lluuidhashmap.h - llversionserver.h - llversionviewer.h + llversionviewer.h.in llworkerthread.h metaclass.h metaclasst.h diff --git a/indra/llcommon/aiframetimer.cpp b/indra/llcommon/aiframetimer.cpp index cb57bb701..3c102a9c7 100644 --- a/indra/llcommon/aiframetimer.cpp +++ b/indra/llcommon/aiframetimer.cpp @@ -27,8 +27,38 @@ * - Initial version, written by Aleric Inglewood @ SL */ -#include "linden_common.h" +// An AIFrameTimer object provides a callback API for timer events. +// +// Typical usage: +// +// // Any thread. +// AIFrameTimer timer; +// +// ... +// // Any thread (after successful construction is guaranteed). +// timer.create(5.5, boost::bind(&the_callback, )); // Call the_callback() in 5.5 seconds. +// +// The callback function is always called by the main thread and should therefore +// be light weight. +// +// If timer.cancel() is called before the timer expires, then the callback +// function isn't called. If cancel() is called by another thread than the +// main thread, then it is possible that the callback function is called +// even while still inside cancel(), but as soon as cancel() returned it +// is guarenteed that the callback function won't be called anymore. +// Hence, if the callback function is a member of some object then +// cancel() must be called before the destruction of that object (ie from +// it's destructor). Calling cancel() multiple times is not a problem. +// Note: if cancel() is called while the callback function is being +// called then cancel() will block until the callback function returned. +// +// The timer object can be reused (by calling create() again), but +// only after either the callback function was called, or after cancel() +// returned. Most notably, you can call create() again from inside the +// callback function to "restart" the timer. +// +#include "linden_common.h" #include "aiframetimer.h" static F64 const NEVER = 1e16; // 317 million years. @@ -115,7 +145,7 @@ void AIFrameTimer::handleExpiration(F64 current_frame_time) // function here because the trylock fails. // // Assuming the main thread arrived here, there are two possible states - // for the other thread that tries to delete the call back function, + // for the other thread that tries to delete the callback function, // right after calling the cancel() function too: // // 1. It hasn't obtained the first lock yet, we obtain the handle.mMutex diff --git a/indra/llcommon/aiframetimer.h b/indra/llcommon/aiframetimer.h index 3b997560d..cb50caec5 100644 --- a/indra/llcommon/aiframetimer.h +++ b/indra/llcommon/aiframetimer.h @@ -61,20 +61,37 @@ class LL_COMMON_API AIFrameTimer // See aiframetimer.cpp for more notes. class AIRunningFrameTimer { private: - F64 mExpire; // Time at which the timer expires, in seconds since application start (compared to LLFrameTimer::sFrameTime). - Signal* mCallback; - AIFrameTimer* mTimer; + F64 mExpire; // Time at which the timer expires, in seconds since application start (compared to LLFrameTimer::sFrameTime). + AIFrameTimer* mTimer; // The actual timer. + // Can be mutable, since only the mExpire is used for ordering this object in the multiset AIFrameTimer::sTimerList. + mutable Signal* mCallback; // Pointer to callback struct, or NULL when the object wasn't added to sTimerList yet. public: - AIRunningFrameTimer(F64 expiration, AIFrameTimer* timer) : mExpire(LLFrameTimer::getElapsedSeconds() + expiration), mCallback(new Signal), mTimer(timer) { } + AIRunningFrameTimer(F64 expiration, AIFrameTimer* timer) : mExpire(LLFrameTimer::getElapsedSeconds() + expiration), mCallback(NULL), mTimer(timer) { } ~AIRunningFrameTimer() { delete mCallback; } - void init(signal_type::slot_type const& slot) const { mCallback->mSignal.connect(slot); } + // This function is called after the final object was added to sTimerList (where it is initialized in-place). + void init(signal_type::slot_type const& slot) const + { + // We may only call init() once. + llassert(!mCallback); + mCallback = new Signal; + mCallback->mSignal.connect(slot); + } + + // Order AIFrameTimer::sTimerList so that the timer that expires first is up front. friend bool operator<(AIRunningFrameTimer const& ft1, AIRunningFrameTimer const& ft2) { return ft1.mExpire < ft2.mExpire; } void do_callback(void) const { mCallback->mSignal(); } F64 expiration(void) const { return mExpire; } AIFrameTimer* getTimer(void) const { return mTimer; } + +#if LL_DEBUG + // May not copy this object after it was initialized. + AIRunningFrameTimer(AIRunningFrameTimer const& running_frame_timer) : + mExpire(running_frame_timer.mExpire), mCallback(running_frame_timer.mCallback), mTimer(running_frame_timer.mTimer) + { llassert(!mCallback); } +#endif }; typedef std::multiset timer_list_type; @@ -98,12 +115,12 @@ class LL_COMMON_API AIFrameTimer // Actual initialization used by AIFrameTimer::create. void init(timer_list_type::iterator const& running_timer, signal_type::slot_type const& slot) - { - // Locking AIFrameTimer::sMutex is not neccessary here, because we're creating - // the object and no other thread knows of mRunningTimer at this point. - mRunningTimer = running_timer; - mRunningTimer->init(slot); - } + { + // Locking AIFrameTimer::sMutex is not neccessary here, because we're creating + // the object and no other thread knows of mRunningTimer at this point. + mRunningTimer = running_timer; + mRunningTimer->init(slot); + } private: // LLMutex has no assignment operator. @@ -129,6 +146,8 @@ class LL_COMMON_API AIFrameTimer void create(F64 expiration, signal_type::slot_type const& slot); void cancel(void); + bool isRunning(void) const { bool running; sMutex.lock(); running = mHandle.mRunningTimer != sTimerList.end(); sMutex.unlock(); return running; } + protected: static void handleExpiration(F64 current_frame_time); }; diff --git a/indra/llcommon/ll_template_cast.h b/indra/llcommon/ll_template_cast.h new file mode 100644 index 000000000..c8f9a2f7e --- /dev/null +++ b/indra/llcommon/ll_template_cast.h @@ -0,0 +1,177 @@ +/** + * @file ll_template_cast.h + * @author Nat Goodspeed + * @date 2009-11-21 + * @brief Define ll_template_cast function + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, 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$ + */ + +#if ! defined(LL_LL_TEMPLATE_CAST_H) +#define LL_LL_TEMPLATE_CAST_H + +/** + * Implementation for ll_template_cast() (q.v.). + * + * Default implementation: trying to cast two completely unrelated types + * returns 0. Typically you'd specify T and U as pointer types, but in fact T + * can be any type that can be initialized with 0. + */ +template +struct ll_template_cast_impl +{ + T operator()(U) + { + return 0; + } +}; + +/** + * ll_template_cast(some_value) is for use in a template function when + * some_value might be of arbitrary type, but you want to recognize type T + * specially. + * + * It's designed for use with pointer types. Example: + * @code + * struct SpecialClass + * { + * void someMethod(const std::string&) const; + * }; + * + * template + * void somefunc(const REALCLASS& instance) + * { + * const SpecialClass* ptr = ll_template_cast(&instance); + * if (ptr) + * { + * ptr->someMethod("Call method only available on SpecialClass"); + * } + * } + * @endcode + * + * Why is this better than dynamic_cast<>? Because unless OtherClass is + * polymorphic, the following won't even compile (gcc 4.0.1): + * @code + * OtherClass other; + * SpecialClass* ptr = dynamic_cast(&other); + * @endcode + * to say nothing of this: + * @code + * void function(int); + * SpecialClass* ptr = dynamic_cast(&function); + * @endcode + * ll_template_cast handles these kinds of cases by returning 0. + */ +template +T ll_template_cast(U value) +{ + return ll_template_cast_impl()(value); +} + +/** + * Implementation for ll_template_cast() (q.v.). + * + * Implementation for identical types: return same value. + */ +template +struct ll_template_cast_impl +{ + T operator()(T value) + { + return value; + } +}; + +/** + * LL_TEMPLATE_CONVERTIBLE(dest, source) asserts that, for a value @c s of + * type @c source, ll_template_cast(s) will return @c s -- + * presuming that @c source can be converted to @c dest by the normal rules of + * C++. + * + * By default, ll_template_cast(s) will return 0 unless @c s's + * type is literally identical to @c dest. (This is because of the + * straightforward application of template specialization rules.) That can + * lead to surprising results, e.g.: + * + * @code + * Foo myFoo; + * const Foo* fooptr = ll_template_cast(&myFoo); + * @endcode + * + * Here @c fooptr will be 0 because &myFoo is of type Foo* + * -- @em not const Foo*. (Declaring const Foo myFoo; would + * force the compiler to do the right thing.) + * + * More disappointingly: + * @code + * struct Base {}; + * struct Subclass: public Base {}; + * Subclass object; + * Base* ptr = ll_template_cast(&object); + * @endcode + * + * Here @c ptr will be 0 because &object is of type + * Subclass* rather than Base*. We @em want this cast to + * succeed, but without our help ll_template_cast can't recognize it. + * + * The following would suffice: + * @code + * LL_TEMPLATE_CONVERTIBLE(Base*, Subclass*); + * ... + * Base* ptr = ll_template_cast(&object); + * @endcode + * + * However, as noted earlier, this is easily fooled: + * @code + * const Base* ptr = ll_template_cast(&object); + * @endcode + * would still produce 0 because we haven't yet seen: + * @code + * LL_TEMPLATE_CONVERTIBLE(const Base*, Subclass*); + * @endcode + * + * @TODO + * This macro should use Boost type_traits facilities for stripping and + * re-adding @c const and @c volatile qualifiers so that invoking + * LL_TEMPLATE_CONVERTIBLE(dest, source) will automatically generate all + * permitted permutations. It's really not fair to the coder to require + * separate: + * @code + * LL_TEMPLATE_CONVERTIBLE(Base*, Subclass*); + * LL_TEMPLATE_CONVERTIBLE(const Base*, Subclass*); + * LL_TEMPLATE_CONVERTIBLE(const Base*, const Subclass*); + * @endcode + * + * (Naturally we omit LL_TEMPLATE_CONVERTIBLE(Base*, const Subclass*) + * because that's not permitted by normal C++ assignment anyway.) + */ +#define LL_TEMPLATE_CONVERTIBLE(DEST, SOURCE) \ +template <> \ +struct ll_template_cast_impl \ +{ \ + DEST operator()(SOURCE wrapper) \ + { \ + return wrapper; \ + } \ +} + +#endif /* ! defined(LL_LL_TEMPLATE_CAST_H) */ diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp new file mode 100644 index 000000000..0b5829eb7 --- /dev/null +++ b/indra/llcommon/llcoros.cpp @@ -0,0 +1,154 @@ +/** + * @file llcoros.cpp + * @author Nat Goodspeed + * @date 2009-06-03 + * @brief Implementation for llcoros. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llcoros.h" +// STL headers +// std headers +// external library headers +#include +// other Linden headers +#include "llevents.h" +#include "llerror.h" +#include "stringize.h" + +LLCoros::LLCoros() +{ + // Register our cleanup() method for "mainloop" ticks + LLEventPumps::instance().obtain("mainloop").listen( + "LLCoros", boost::bind(&LLCoros::cleanup, this, _1)); +} + +bool LLCoros::cleanup(const LLSD&) +{ + // Walk the mCoros map, checking and removing completed coroutines. + for (CoroMap::iterator mi(mCoros.begin()), mend(mCoros.end()); mi != mend; ) + { + // Has this coroutine exited (normal return, exception, exit() call) + // since last tick? + if (mi->second->exited()) + { + LL_INFOS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << LL_ENDL; + // The erase() call will invalidate its passed iterator value -- + // so increment mi FIRST -- but pass its original value to + // erase(). This is what postincrement is all about. + mCoros.erase(mi++); + } + else + { + // Still live, just skip this entry as if incrementing at the top + // of the loop as usual. + ++mi; + } + } + return false; +} + +std::string LLCoros::generateDistinctName(const std::string& prefix) const +{ + // Allowing empty name would make getName()'s not-found return ambiguous. + if (prefix.empty()) + { + LL_ERRS("LLCoros") << "LLCoros::launch(): pass non-empty name string" << LL_ENDL; + } + + // If the specified name isn't already in the map, just use that. + std::string name(prefix); + + // Find the lowest numeric suffix that doesn't collide with an existing + // entry. Start with 2 just to make it more intuitive for any interested + // parties: e.g. "joe", "joe2", "joe3"... + for (int i = 2; ; name = STRINGIZE(prefix << i++)) + { + if (mCoros.find(name) == mCoros.end()) + { + LL_INFOS("LLCoros") << "LLCoros: launching coroutine " << name << LL_ENDL; + return name; + } + } +} + +bool LLCoros::kill(const std::string& name) +{ + CoroMap::iterator found = mCoros.find(name); + if (found == mCoros.end()) + { + return false; + } + // Because this is a boost::ptr_map, erasing the map entry also destroys + // the referenced heap object, in this case the boost::coroutine object, + // which will terminate the coroutine. + mCoros.erase(found); + return true; +} + +std::string LLCoros::getNameByID(const void* self_id) const +{ + // Walk the existing coroutines, looking for one from which the 'self_id' + // passed to us comes. + for (CoroMap::const_iterator mi(mCoros.begin()), mend(mCoros.end()); mi != mend; ++mi) + { + namespace coro_private = boost::coroutines::detail; + if (static_cast(coro_private::coroutine_accessor::get_impl(const_cast(*mi->second)).get()) + == self_id) + { + return mi->first; + } + } + return ""; +} + +/***************************************************************************** +* MUST BE LAST +*****************************************************************************/ +// Turn off MSVC optimizations for just LLCoros::launchImpl() -- see +// DEV-32777. But MSVC doesn't support push/pop for optimization flags as it +// does for warning suppression, and we really don't want to force +// optimization ON for other code even in Debug or RelWithDebInfo builds. + +#if LL_MSVC +// work around broken optimizations +#pragma warning(disable: 4748) +#pragma optimize("", off) +#endif // LL_MSVC + +std::string LLCoros::launchImpl(const std::string& prefix, coro* newCoro) +{ + std::string name(generateDistinctName(prefix)); + mCoros.insert(name, newCoro); + /* Run the coroutine until its first wait, then return here */ + (*newCoro)(std::nothrow); + return name; +} + +#if LL_MSVC +// reenable optimizations +#pragma optimize("", on) +#endif // LL_MSVC diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h new file mode 100644 index 000000000..d75f28ec1 --- /dev/null +++ b/indra/llcommon/llcoros.h @@ -0,0 +1,166 @@ +/** + * @file llcoros.h + * @author Nat Goodspeed + * @date 2009-06-02 + * @brief Manage running boost::coroutine instances + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, 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$ + */ + +#if ! defined(LL_LLCOROS_H) +#define LL_LLCOROS_H + +#include +#include "llsingleton.h" +#include +#include +#include +#include +#include +#include + +/** + * Registry of named Boost.Coroutine instances + * + * The Boost.Coroutine library supports the general case of a coroutine + * accepting arbitrary parameters and yielding multiple (sets of) results. For + * such use cases, it's natural for the invoking code to retain the coroutine + * instance: the consumer repeatedly calls into the coroutine, perhaps passing + * new parameter values, prompting it to yield its next result. + * + * Our typical coroutine usage is different, though. For us, coroutines + * provide an alternative to the @c Responder pattern. Our typical coroutine + * has @c void return, invoked in fire-and-forget mode: the handler for some + * user gesture launches the coroutine and promptly returns to the main loop. + * The coroutine initiates some action that will take multiple frames (e.g. a + * capability request), waits for its result, processes it and silently steals + * away. + * + * This usage poses two (related) problems: + * + * # Who should own the coroutine instance? If it's simply local to the + * handler code that launches it, return from the handler will destroy the + * coroutine object, terminating the coroutine. + * # Once the coroutine terminates, in whatever way, who's responsible for + * cleaning up the coroutine object? + * + * LLCoros is a Singleton collection of currently-active coroutine instances. + * Each has a name. You ask LLCoros to launch a new coroutine with a suggested + * name prefix; from your prefix it generates a distinct name, registers the + * new coroutine and returns the actual name. + * + * The name can be used to kill off the coroutine prematurely, if needed. It + * can also provide diagnostic info: we can look up the name of the + * currently-running coroutine. + * + * Finally, the next frame ("mainloop" event) after the coroutine terminates, + * LLCoros will notice its demise and destroy it. + */ +class LL_COMMON_API LLCoros: public LLSingleton +{ +public: + /// Canonical boost::coroutines::coroutine signature we use + typedef boost::coroutines::coroutine coro; + /// Canonical 'self' type + typedef coro::self self; + + /** + * Create and start running a new coroutine with specified name. The name + * string you pass is a suggestion; it will be tweaked for uniqueness. The + * actual name is returned to you. + * + * Usage looks like this, for (e.g.) two coroutine parameters: + * @code + * class MyClass + * { + * public: + * ... + * // Do NOT NOT NOT accept reference params other than 'self'! + * // Pass by value only! + * void myCoroutineMethod(LLCoros::self& self, std::string, LLSD); + * ... + * }; + * ... + * std::string name = LLCoros::instance().launch( + * "mycoro", boost::bind(&MyClass::myCoroutineMethod, this, _1, + * "somestring", LLSD(17)); + * @endcode + * + * Your function/method must accept LLCoros::self& as its first parameter. + * It can accept any other parameters you want -- but ONLY BY VALUE! + * Other reference parameters are a BAD IDEA! You Have Been Warned. See + * DEV-32777 comments for an explanation. + * + * Pass a callable that accepts the single LLCoros::self& parameter. It + * may work to pass a free function whose only parameter is 'self'; for + * all other cases use boost::bind(). Of course, for a non-static class + * method, the first parameter must be the class instance. Use the + * placeholder _1 for the 'self' parameter. Any other parameters should be + * passed via the bind() expression. + * + * launch() tweaks the suggested name so it won't collide with any + * existing coroutine instance, creates the coroutine instance, registers + * it with the tweaked name and runs it until its first wait. At that + * point it returns the tweaked name. + */ + template + std::string launch(const std::string& prefix, const CALLABLE& callable) + { + return launchImpl(prefix, new coro(callable)); + } + + /** + * Abort a running coroutine by name. Normally, when a coroutine either + * runs to completion or terminates with an exception, LLCoros quietly + * cleans it up. This is for use only when you must explicitly interrupt + * one prematurely. Returns @c true if the specified name was found and + * still running at the time. + */ + bool kill(const std::string& name); + + /** + * From within a coroutine, pass its @c self object to look up the + * (tweaked) name string by which this coroutine is registered. Returns + * the empty string if not found (e.g. if the coroutine was launched by + * hand rather than using LLCoros::launch()). + */ + template + std::string getName(const COROUTINE_SELF& self) const + { + return getNameByID(self.get_id()); + } + + /// getName() by self.get_id() + std::string getNameByID(const void* self_id) const; + +private: + friend class LLSingleton; + LLCoros(); + std::string launchImpl(const std::string& prefix, coro* newCoro); + std::string generateDistinctName(const std::string& prefix) const; + bool cleanup(const LLSD&); + + typedef boost::ptr_map CoroMap; + CoroMap mCoros; +}; + +#endif /* ! defined(LL_LLCOROS_H) */ diff --git a/indra/llcommon/lldependencies.cpp b/indra/llcommon/lldependencies.cpp new file mode 100644 index 000000000..0e72c175c --- /dev/null +++ b/indra/llcommon/lldependencies.cpp @@ -0,0 +1,103 @@ +/** + * @file lldependencies.cpp + * @author Nat Goodspeed + * @date 2008-09-17 + * @brief Implementation for lldependencies. + * + * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lldependencies.h" +// STL headers +#include +#include +// std headers +// external library headers +#include // for boost::graph_traits +#include +#include +#include +// other Linden headers + +LLDependenciesBase::VertexList LLDependenciesBase::topo_sort(int vertices, const EdgeList& edges) const +{ + // Construct a Boost Graph Library graph according to the constraints + // we've collected. It seems as though we ought to be able to capture + // the uniqueness of vertex keys using a setS of vertices with a + // string property -- but I don't yet understand adjacency_list well + // enough to get there. All the examples I've seen so far use integers + // for vertices. + // Define the Graph type. Use a vector for vertices so we can use the + // default topological_sort vertex lookup by int index. Use a set for + // edges because the same dependency may be stated twice: Node "a" may + // specify that it must precede "b", while "b" may also state that it + // must follow "a". + typedef boost::adjacency_list Graph; + // Instantiate the graph. Without vertex properties, we need say no + // more about vertices than the total number. + Graph g(edges.begin(), edges.end(), vertices); + // topo sort + typedef boost::graph_traits::vertex_descriptor VertexDesc; + typedef std::vector SortedList; + SortedList sorted; + // note that it throws not_a_dag if it finds a cycle + try + { + boost::topological_sort(g, std::back_inserter(sorted)); + } + catch (const boost::not_a_dag& e) + { + // translate to the exception we define + std::ostringstream out; + out << "LLDependencies cycle: " << e.what() << '\n'; + // Omit independent nodes: display only those that might contribute to + // the cycle. + describe(out, false); + throw Cycle(out.str()); + } + // A peculiarity of boost::topological_sort() is that it emits results in + // REVERSE topological order: to get the result you want, you must + // traverse the SortedList using reverse iterators. + return VertexList(sorted.rbegin(), sorted.rend()); +} + +std::ostream& LLDependenciesBase::describe(std::ostream& out, bool full) const +{ + // Should never encounter this base-class implementation; may mean that + // the KEY type doesn't have a suitable ostream operator<<(). + out << ""; + return out; +} + +std::string LLDependenciesBase::describe(bool full) const +{ + // Just use the ostream-based describe() on a std::ostringstream. The + // implementation is here mostly so that we can avoid #include + // in the header file. + std::ostringstream out; + describe(out, full); + return out.str(); +} diff --git a/indra/llcommon/lldependencies.h b/indra/llcommon/lldependencies.h new file mode 100644 index 000000000..e0294e271 --- /dev/null +++ b/indra/llcommon/lldependencies.h @@ -0,0 +1,799 @@ +/** + * @file lldependencies.h + * @author Nat Goodspeed + * @date 2008-09-17 + * @brief LLDependencies: a generic mechanism for expressing "b must follow a, + * but precede c" + * + * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, 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$ + */ + +#if ! defined(LL_LLDEPENDENCIES_H) +#define LL_LLDEPENDENCIES_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/***************************************************************************** +* Utilities +*****************************************************************************/ +/** + * generic range transformer: given a range acceptable to Boost.Range (e.g. a + * standard container, an iterator pair, ...) and a unary function to apply to + * each element of the range, make a corresponding range that lazily applies + * that function to each element on dereferencing. + */ +template +inline +boost::iterator_range::type> > +make_transform_range(const RANGE& range, FUNCTION function) +{ + // shorthand for the iterator type embedded in our return type + typedef boost::transform_iterator::type> + transform_iterator; + return boost::make_iterator_range(transform_iterator(boost::begin(range), function), + transform_iterator(boost::end(range), function)); +} + +/// non-const version of make_transform_range() +template +inline +boost::iterator_range::type> > +make_transform_range(RANGE& range, FUNCTION function) +{ + // shorthand for the iterator type embedded in our return type + typedef boost::transform_iterator::type> + transform_iterator; + return boost::make_iterator_range(transform_iterator(boost::begin(range), function), + transform_iterator(boost::end(range), function)); +} + +/** + * From any range compatible with Boost.Range, instantiate any class capable + * of accepting an iterator pair. + */ +template +struct instance_from_range: public TYPE +{ + template + instance_from_range(RANGE range): + TYPE(boost::begin(range), boost::end(range)) + {} +}; + +/***************************************************************************** +* LLDependencies +*****************************************************************************/ +/** + * LLDependencies components that should not be reinstantiated for each KEY, + * NODE specialization + */ +class LL_COMMON_API LLDependenciesBase +{ +public: + virtual ~LLDependenciesBase() {} + + /** + * Exception thrown by sort() if there's a cycle + */ + struct Cycle: public std::runtime_error + { + Cycle(const std::string& what): std::runtime_error(what) {} + }; + + /** + * Provide a short description of this LLDependencies instance on the + * specified output stream, assuming that its KEY type has an operator<<() + * that works with std::ostream. + * + * Pass @a full as @c false to omit any keys without dependency constraints. + */ + virtual std::ostream& describe(std::ostream& out, bool full=true) const; + + /// describe() to a string + virtual std::string describe(bool full=true) const; + +protected: + typedef std::vector< std::pair > EdgeList; + typedef std::vector VertexList; + VertexList topo_sort(int vertices, const EdgeList& edges) const; + + /** + * refpair is specifically intended to capture a pair of references. This + * is better than std::pair because some implementations of + * std::pair's ctor accept const references to the two types. If the + * types are themselves references, this results in an illegal reference- + * to-reference. + */ + template + struct refpair + { + refpair(T1 value1, T2 value2): + first(value1), + second(value2) + {} + T1 first; + T2 second; + }; +}; + +/// describe() helper: for most types, report the type as usual +template +inline +std::ostream& LLDependencies_describe(std::ostream& out, const T& key) +{ + out << key; + return out; +} + +/// specialize LLDependencies_describe() for std::string +template<> +inline +std::ostream& LLDependencies_describe(std::ostream& out, const std::string& key) +{ + out << '"' << key << '"'; + return out; +} + +/** + * It's reasonable to use LLDependencies in a keys-only way, more or less like + * std::set. For that case, the default NODE type is an empty struct. + */ +struct LLDependenciesEmpty +{ + LLDependenciesEmpty() {} + /** + * Give it a constructor accepting void* so caller can pass placeholder + * values such as NULL or 0 rather than having to write + * LLDependenciesEmpty(). + */ + LLDependenciesEmpty(void*) {} +}; + +/** + * This class manages abstract dependencies between node types of your + * choosing. As with a std::map, nodes are copied when add()ed, so the node + * type should be relatively lightweight; to manipulate dependencies between + * expensive objects, use a pointer type. + * + * For a given node, you may state the keys of nodes that must precede it + * and/or nodes that must follow it. The sort() method will produce an order + * that should work, or throw an exception if the constraints are impossible. + * We cache results to minimize the cost of repeated sort() calls. + */ +template +class LLDependencies: public LLDependenciesBase +{ + typedef LLDependencies self_type; + + /** + * Internally, we bundle the client's NODE with its before/after keys. + */ + struct DepNode + { + typedef std::set dep_set; + DepNode(const NODE& node_, const dep_set& after_, const dep_set& before_): + node(node_), + after(after_), + before(before_) + {} + NODE node; + dep_set after, before; + }; + typedef std::map DepNodeMap; + typedef typename DepNodeMap::value_type DepNodeMapEntry; + + /// We have various ways to get the dependencies for a given DepNode. + /// Rather than having to restate each one for 'after' and 'before' + /// separately, pass a dep_selector so we can apply each to either. + typedef boost::function dep_selector; + +public: + LLDependencies() {} + + typedef KEY key_type; + typedef NODE node_type; + + /// param type used to express lists of other node keys -- note that such + /// lists can be initialized with boost::assign::list_of() + typedef std::vector KeyList; + + /** + * Add a new node. State its dependencies on other nodes (which may not + * yet have been added) by listing the keys of nodes this new one must + * follow, and separately the keys of nodes this new one must precede. + * + * The node you pass is @em copied into an internal data structure. If you + * want to modify the node value after add()ing it, capture the returned + * NODE& reference. + * + * @note + * Actual dependency analysis is deferred to the sort() method, so + * you can add an arbitrary number of nodes without incurring analysis + * overhead for each. The flip side of this is that add()ing nodes that + * define a cycle leaves this object in a state in which sort() will + * always throw the Cycle exception. + * + * Two distinct use cases are anticipated: + * * The data used to load this object are completely known at compile + * time (e.g. LLEventPump listener names). A Cycle exception represents a + * bug which can be corrected by the coder. The program need neither catch + * Cycle nor attempt to salvage the state of this object. + * * The data are loaded at runtime, therefore the universe of + * dependencies cannot be known at compile time. The client code should + * catch Cycle. + * ** If a Cycle exception indicates fatally-flawed input data, this + * object can simply be discarded, possibly with the entire program run. + * ** If it is essential to restore this object to a working state, the + * simplest workaround is to remove() nodes in LIFO order. + * *** It may be useful to add functionality to this class to track the + * add() chronology, providing a pop() method to remove the most recently + * added node. + * *** It may further be useful to add a restore() method which would + * pop() until sort() no longer throws Cycle. This method would be + * expensive -- but it's not clear that client code could resolve the + * problem more cheaply. + */ + NODE& add(const KEY& key, const NODE& node = NODE(), + const KeyList& after = KeyList(), + const KeyList& before = KeyList()) + { + // Get the passed-in lists as sets for equality comparison + typename DepNode::dep_set + after_set(after.begin(), after.end()), + before_set(before.begin(), before.end()); + // Try to insert the new node; if it already exists, find the old + // node instead. + std::pair inserted = + mNodes.insert(typename DepNodeMap::value_type(key, + DepNode(node, after_set, before_set))); + if (! inserted.second) // bool indicating success of insert() + { + // We already have a node by this name. Have its dependencies + // changed? If the existing node's dependencies are identical, the + // result will be unchanged, so we can leave the cache intact. + // Regardless of inserted.second, inserted.first is the iterator + // to the newly-inserted (or existing) map entry. Of course, that + // entry's second is the DepNode of interest. + if (inserted.first->second.after != after_set || + inserted.first->second.before != before_set) + { + // Dependencies have changed: clear the cached result. + mCache.clear(); + // save the new dependencies + inserted.first->second.after = after_set; + inserted.first->second.before = before_set; + } + } + else // this node is new + { + // This will change results. + mCache.clear(); + } + return inserted.first->second.node; + } + + /// the value of an iterator, showing both KEY and its NODE + typedef refpair value_type; + /// the value of a const_iterator + typedef refpair const_value_type; + +private: + // Extract functors + static value_type value_extract(DepNodeMapEntry& entry) + { + return value_type(entry.first, entry.second.node); + } + + static const_value_type const_value_extract(const DepNodeMapEntry& entry) + { + return const_value_type(entry.first, entry.second.node); + } + + // All the iterator access methods return iterator ranges just to cut down + // on the friggin' boilerplate!! + + /// generic mNodes range method + template + boost::iterator_range generic_range(FUNCTION function) + { + return make_transform_range(mNodes, function); + } + + /// generic mNodes const range method + template + boost::iterator_range generic_range(FUNCTION function) const + { + return make_transform_range(mNodes, function); + } + +public: + /// iterator over value_type entries + typedef boost::transform_iterator, + typename DepNodeMap::iterator> iterator; + /// range over value_type entries + typedef boost::iterator_range range; + + /// iterate over value_type in @c KEY order rather than dependency order + range get_range() + { + return generic_range(value_extract); + } + + /// iterator over const_value_type entries + typedef boost::transform_iterator, + typename DepNodeMap::const_iterator> const_iterator; + /// range over const_value_type entries + typedef boost::iterator_range const_range; + + /// iterate over const_value_type in @c KEY order rather than dependency order + const_range get_range() const + { + return generic_range(const_value_extract); + } + + /// iterator over stored NODEs + typedef boost::transform_iterator, + typename DepNodeMap::iterator> node_iterator; + /// range over stored NODEs + typedef boost::iterator_range node_range; + + /// iterate over NODE in @c KEY order rather than dependency order + node_range get_node_range() + { + // First take a DepNodeMapEntry and extract a reference to its + // DepNode, then from that extract a reference to its NODE. + return generic_range( + boost::bind(&DepNode::node, + boost::bind(&DepNodeMapEntry::second, _1))); + } + + /// const iterator over stored NODEs + typedef boost::transform_iterator, + typename DepNodeMap::const_iterator> const_node_iterator; + /// const range over stored NODEs + typedef boost::iterator_range const_node_range; + + /// iterate over const NODE in @c KEY order rather than dependency order + const_node_range get_node_range() const + { + // First take a DepNodeMapEntry and extract a reference to its + // DepNode, then from that extract a reference to its NODE. + return generic_range( + boost::bind(&DepNode::node, + boost::bind(&DepNodeMapEntry::second, _1))); + } + + /// const iterator over stored KEYs + typedef boost::transform_iterator, + typename DepNodeMap::const_iterator> const_key_iterator; + /// const range over stored KEYs + typedef boost::iterator_range const_key_range; + // We don't provide a non-const iterator over KEYs because they should be + // immutable, and in fact our underlying std::map won't give us non-const + // references. + + /// iterate over const KEY in @c KEY order rather than dependency order + const_key_range get_key_range() const + { + // From a DepNodeMapEntry, extract a reference to its KEY. + return generic_range( + boost::bind(&DepNodeMapEntry::first, _1)); + } + + /** + * Find an existing NODE, or return NULL. We decided to avoid providing a + * method analogous to std::map::find(), for a couple of reasons: + * + * * For a find-by-key, getting back an iterator to the (key, value) pair + * is less than useful, since you already have the key in hand. + * * For a failed request, comparing to end() is problematic. First, we + * provide range accessors, so it's more code to get end(). Second, we + * provide a number of different ranges -- quick, to which one's end() + * should we compare the iterator returned by find()? + * + * The returned pointer is solely to allow expressing the not-found + * condition. LLDependencies still owns the found NODE. + */ + const NODE* get(const KEY& key) const + { + typename DepNodeMap::const_iterator found = mNodes.find(key); + if (found != mNodes.end()) + { + return &found->second.node; + } + return NULL; + } + + /** + * non-const get() + */ + NODE* get(const KEY& key) + { + // Use const implementation, then cast away const-ness of return + return const_cast(const_cast(this)->get(key)); + } + + /** + * Remove a node with specified key. This operation is the major reason + * we rebuild the graph on the fly instead of storing it. + */ + bool remove(const KEY& key) + { + typename DepNodeMap::iterator found = mNodes.find(key); + if (found != mNodes.end()) + { + mNodes.erase(found); + return true; + } + return false; + } + +private: + /// cached list of iterators + typedef std::vector iterator_list; + typedef typename iterator_list::iterator iterator_list_iterator; + +public: + /** + * The return type of the sort() method needs some explanation. Provide a + * public typedef to facilitate storing the result. + * + * * We will prepare mCache by looking up DepNodeMap iterators. + * * We want to return a range containing iterators that will walk mCache. + * * If we simply stored DepNodeMap iterators and returned + * (mCache.begin(), mCache.end()), dereferencing each iterator would + * obtain a DepNodeMap iterator. + * * We want the caller to loop over @c value_type: pair. + * * This requires two transformations: + * ** mCache must contain @c LLDependencies::iterator so that + * dereferencing each entry will obtain an @c LLDependencies::value_type + * rather than a DepNodeMapEntry. + * ** We must wrap mCache's iterators in boost::indirect_iterator so that + * dereferencing one of our returned iterators will also dereference the + * iterator contained in mCache. + */ + typedef boost::iterator_range > sorted_range; + /// for convenience in looping over a sorted_range + typedef typename sorted_range::iterator sorted_iterator; + + /** + * Once we've loaded in the dependencies of interest, arrange them into an + * order that works -- or throw Cycle exception. + * + * Return an iterator range over (key, node) pairs that traverses them in + * the desired order. + */ + sorted_range sort() const + { + // Changes to mNodes cause us to clear our cache, so empty mCache + // means it's invalid and should be recomputed. However, if mNodes is + // also empty, then an empty mCache represents a valid order, so don't + // bother sorting. + if (mCache.empty() && ! mNodes.empty()) + { + // Construct a map of node keys to distinct vertex numbers -- even for + // nodes mentioned only in before/after constraints, that haven't yet + // been explicitly added. Rely on std::map rejecting a second attempt + // to insert the same key. Use the map's size() as the vertex number + // to get a distinct value for each successful insertion. + typedef std::map VertexMap; + VertexMap vmap; + // Nest each of these loops because !@#$%? MSVC warns us that its + // former broken behavior has finally been fixed -- and our builds + // treat warnings as errors. + { + for (typename DepNodeMap::const_iterator nmi = mNodes.begin(), nmend = mNodes.end(); + nmi != nmend; ++nmi) + { + vmap.insert(typename VertexMap::value_type(nmi->first, vmap.size())); + for (typename DepNode::dep_set::const_iterator ai = nmi->second.after.begin(), + aend = nmi->second.after.end(); + ai != aend; ++ai) + { + vmap.insert(typename VertexMap::value_type(*ai, vmap.size())); + } + for (typename DepNode::dep_set::const_iterator bi = nmi->second.before.begin(), + bend = nmi->second.before.end(); + bi != bend; ++bi) + { + vmap.insert(typename VertexMap::value_type(*bi, vmap.size())); + } + } + } + // Define the edges. For this we must traverse mNodes again, mapping + // all the known key dependencies to integer pairs. + EdgeList edges; + { + for (typename DepNodeMap::const_iterator nmi = mNodes.begin(), nmend = mNodes.end(); + nmi != nmend; ++nmi) + { + int thisnode = vmap[nmi->first]; + // after dependencies: build edges from the named node to this one + for (typename DepNode::dep_set::const_iterator ai = nmi->second.after.begin(), + aend = nmi->second.after.end(); + ai != aend; ++ai) + { + edges.push_back(EdgeList::value_type(vmap[*ai], thisnode)); + } + // before dependencies: build edges from this node to the + // named one + for (typename DepNode::dep_set::const_iterator bi = nmi->second.before.begin(), + bend = nmi->second.before.end(); + bi != bend; ++bi) + { + edges.push_back(EdgeList::value_type(thisnode, vmap[*bi])); + } + } + } + // Hide the gory details of our topological sort, since they shouldn't + // get reinstantiated for each distinct NODE type. + VertexList sorted(topo_sort(vmap.size(), edges)); + // Build the reverse of vmap to look up the key for each vertex + // descriptor. vmap contains exactly one entry for each distinct key, + // and we're certain that the associated int values are distinct + // indexes. The fact that they're not in order is irrelevant. + KeyList vkeys(vmap.size()); + for (typename VertexMap::const_iterator vmi = vmap.begin(), vmend = vmap.end(); + vmi != vmend; ++vmi) + { + vkeys[vmi->second] = vmi->first; + } + // Walk the sorted output list, building the result into mCache so + // we'll have it next time someone asks. + mCache.clear(); + for (VertexList::const_iterator svi = sorted.begin(), svend = sorted.end(); + svi != svend; ++svi) + { + // We're certain that vkeys[*svi] exists. However, there might not + // yet be a corresponding entry in mNodes. + self_type* non_const_this(const_cast(this)); + typename DepNodeMap::iterator found = non_const_this->mNodes.find(vkeys[*svi]); + if (found != non_const_this->mNodes.end()) + { + // Make an iterator of appropriate type. + mCache.push_back(iterator(found, value_extract)); + } + } + } + // Whether or not we've just recomputed mCache, it should now contain + // the results we want. Return a range of indirect_iterators over it + // so that dereferencing a returned iterator will dereference the + // iterator stored in mCache and directly reference the (key, node) + // pair. + boost::indirect_iterator + begin(mCache.begin()), + end(mCache.end()); + return sorted_range(begin, end); + } + + using LLDependenciesBase::describe; // unhide virtual std::string describe(bool full=true) const; + + /// Override base-class describe() with actual implementation + virtual std::ostream& describe(std::ostream& out, bool full=true) const + { + typename DepNodeMap::const_iterator dmi(mNodes.begin()), dmend(mNodes.end()); + if (dmi != dmend) + { + std::string sep; + describe(out, sep, *dmi, full); + while (++dmi != dmend) + { + describe(out, sep, *dmi, full); + } + } + return out; + } + + + /// describe() helper: report a DepNodeEntry + static std::ostream& describe(std::ostream& out, std::string& sep, + const DepNodeMapEntry& entry, bool full) + { + // If we were asked for a full report, describe every node regardless + // of whether it has dependencies. If we were asked to suppress + // independent nodes, describe this one if either after or before is + // non-empty. + if (full || (! entry.second.after.empty()) || (! entry.second.before.empty())) + { + out << sep; + sep = "\n"; + if (! entry.second.after.empty()) + { + out << "after "; + describe(out, entry.second.after); + out << " -> "; + } + LLDependencies_describe(out, entry.first); + if (! entry.second.before.empty()) + { + out << " -> before "; + describe(out, entry.second.before); + } + } + return out; + } + + /// describe() helper: report a dep_set + static std::ostream& describe(std::ostream& out, const typename DepNode::dep_set& keys) + { + out << '('; + typename DepNode::dep_set::const_iterator ki(keys.begin()), kend(keys.end()); + if (ki != kend) + { + LLDependencies_describe(out, *ki); + while (++ki != kend) + { + out << ", "; + LLDependencies_describe(out, *ki); + } + } + out << ')'; + return out; + } + + /// Iterator over the before/after KEYs on which a given NODE depends + typedef typename DepNode::dep_set::const_iterator dep_iterator; + /// range over the before/after KEYs on which a given NODE depends + typedef boost::iterator_range dep_range; + + /// dependencies access from key + dep_range get_dep_range_from_key(const KEY& key, const dep_selector& selector) const + { + typename DepNodeMap::const_iterator found = mNodes.find(key); + if (found != mNodes.end()) + { + return dep_range(selector(found->second)); + } + // We want to return an empty range. On some platforms a default- + // constructed range (e.g. dep_range()) does NOT suffice! The client + // is likely to try to iterate from boost::begin(range) to + // boost::end(range); yet these iterators might not be valid. Instead + // return a range over a valid, empty container. + static const typename DepNode::dep_set empty_deps; + return dep_range(empty_deps.begin(), empty_deps.end()); + } + + /// dependencies access from any one of our key-order iterators + template + dep_range get_dep_range_from_xform(const ITERATOR& iterator, const dep_selector& selector) const + { + return dep_range(selector(iterator.base()->second)); + } + + /// dependencies access from sorted_iterator + dep_range get_dep_range_from_sorted(const sorted_iterator& sortiter, + const dep_selector& selector) const + { + // sorted_iterator is a boost::indirect_iterator wrapping an mCache + // iterator, which we can obtain by sortiter.base(). Deferencing that + // gets us an mCache entry, an 'iterator' -- one of our traversal + // iterators -- on which we can use get_dep_range_from_xform(). + return get_dep_range_from_xform(*sortiter.base(), selector); + } + + /** + * Get a range over the after KEYs stored for the passed KEY or iterator, + * in arbitrary order. If you pass a nonexistent KEY, returns empty + * range -- same as a KEY with no after KEYs. Detect existence of a KEY + * using get() instead. + */ + template + dep_range get_after_range(const KEY_OR_ITER& key) const; + + /** + * Get a range over the before KEYs stored for the passed KEY or iterator, + * in arbitrary order. If you pass a nonexistent KEY, returns empty + * range -- same as a KEY with no before KEYs. Detect existence of a KEY + * using get() instead. + */ + template + dep_range get_before_range(const KEY_OR_ITER& key) const; + +private: + DepNodeMap mNodes; + mutable iterator_list mCache; +}; + +/** + * Functor to get a dep_range from a KEY or iterator -- generic case. If the + * passed value isn't one of our iterator specializations, assume it's + * convertible to the KEY type. + */ +template +struct LLDependencies_dep_range_from +{ + template + typename LLDependencies::dep_range + operator()(const LLDependencies& deps, + const KEY_ITER& key, + const SELECTOR& selector) + { + return deps.get_dep_range_from_key(key, selector); + } +}; + +/// Specialize LLDependencies_dep_range_from for our key-order iterators +template +struct LLDependencies_dep_range_from< boost::transform_iterator > +{ + template + typename LLDependencies::dep_range + operator()(const LLDependencies& deps, + const boost::transform_iterator& iter, + const SELECTOR& selector) + { + return deps.get_dep_range_from_xform(iter, selector); + } +}; + +/// Specialize LLDependencies_dep_range_from for sorted_iterator +template +struct LLDependencies_dep_range_from< boost::indirect_iterator > +{ + template + typename LLDependencies::dep_range + operator()(const LLDependencies& deps, + const boost::indirect_iterator& iter, + const SELECTOR& selector) + { + return deps.get_dep_range_from_sorted(iter, selector); + } +}; + +/// generic get_after_range() implementation +template +template +typename LLDependencies::dep_range +LLDependencies::get_after_range(const KEY_OR_ITER& key_iter) const +{ + return LLDependencies_dep_range_from()( + *this, + key_iter, + boost::bind(&DepNode::after, _1)); +} + +/// generic get_before_range() implementation +template +template +typename LLDependencies::dep_range +LLDependencies::get_before_range(const KEY_OR_ITER& key_iter) const +{ + return LLDependencies_dep_range_from()( + *this, + key_iter, + boost::bind(&DepNode::before, _1)); +} + +#endif /* ! defined(LL_LLDEPENDENCIES_H) */ diff --git a/indra/llcommon/llevent.cpp b/indra/llcommon/llevent.cpp index 24be6e8b3..f669d0e13 100644 --- a/indra/llcommon/llevent.cpp +++ b/indra/llcommon/llevent.cpp @@ -34,6 +34,8 @@ #include "llevent.h" +using namespace LLOldEvents; + /************************************************ Events ************************************************/ diff --git a/indra/llcommon/llevent.h b/indra/llcommon/llevent.h index 6b223a8ee..0ea7cf4ae 100644 --- a/indra/llcommon/llevent.h +++ b/indra/llcommon/llevent.h @@ -35,9 +35,12 @@ #define LL_EVENT_H #include "llsd.h" -#include "llmemory.h" +#include "llpointer.h" #include "llthread.h" +namespace LLOldEvents +{ + class LLEventListener; class LLEvent; class LLEventDispatcher; @@ -194,4 +197,6 @@ public: LLSD mValue; }; +} // LLOldEvents + #endif // LL_EVENT_H diff --git a/indra/llcommon/lleventapi.cpp b/indra/llcommon/lleventapi.cpp new file mode 100644 index 000000000..ff5459c1e --- /dev/null +++ b/indra/llcommon/lleventapi.cpp @@ -0,0 +1,77 @@ +/** + * @file lleventapi.cpp + * @author Nat Goodspeed + * @date 2009-11-10 + * @brief Implementation for lleventapi. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lleventapi.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "llerror.h" + +LLEventAPI::LLEventAPI(const std::string& name, const std::string& desc, const std::string& field): + lbase(name, field), + ibase(name), + mDesc(desc) +{ +} + +LLEventAPI::~LLEventAPI() +{ +} + +LLEventAPI::Response::Response(const LLSD& seed, const LLSD& request, const LLSD::String& replyKey): + mResp(seed), + mReq(request), + mKey(replyKey) +{} + +LLEventAPI::Response::~Response() +{ + // When you instantiate a stack Response object, if the original + // request requested a reply, send it when we leave this block, no + // matter how. + sendReply(mResp, mReq, mKey); +} + +void LLEventAPI::Response::warn(const std::string& warning) +{ + LL_WARNS("LLEventAPI::Response") << warning << LL_ENDL; + mResp["warnings"].append(warning); +} + +void LLEventAPI::Response::error(const std::string& error) +{ + // Use LL_WARNS rather than LL_ERROR: we don't want the viewer to shut + // down altogether. + LL_WARNS("LLEventAPI::Response") << error << LL_ENDL; + + mResp["error"] = error; +} diff --git a/indra/llcommon/lleventapi.h b/indra/llcommon/lleventapi.h new file mode 100644 index 000000000..1a37d780b --- /dev/null +++ b/indra/llcommon/lleventapi.h @@ -0,0 +1,166 @@ +/** + * @file lleventapi.h + * @author Nat Goodspeed + * @date 2009-10-28 + * @brief LLEventAPI is the base class for every class that wraps a C++ API + * in an event API + * (see https://wiki.lindenlab.com/wiki/Incremental_Viewer_Automation/Event_API). + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, 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$ + */ + +#if ! defined(LL_LLEVENTAPI_H) +#define LL_LLEVENTAPI_H + +#include "lleventdispatcher.h" +#include "llinstancetracker.h" +#include + +/** + * LLEventAPI not only provides operation dispatch functionality, inherited + * from LLDispatchListener -- it also gives us event API introspection. + * Deriving from LLInstanceTracker lets us enumerate instances. + */ +class LL_COMMON_API LLEventAPI: public LLDispatchListener, + public LLInstanceTracker +{ + typedef LLDispatchListener lbase; + typedef LLInstanceTracker ibase; + +public: + /** + * @param name LLEventPump name on which this LLEventAPI will listen. This + * also serves as the LLInstanceTracker instance key. + * @param desc Documentation string shown to a client trying to discover + * available event APIs. + * @param field LLSD::Map key used by LLDispatchListener to look up the + * subclass method to invoke [default "op"]. + */ + LLEventAPI(const std::string& name, const std::string& desc, const std::string& field="op"); + virtual ~LLEventAPI(); + + /// Get the string name of this LLEventAPI + std::string getName() const { return ibase::getKey(); } + /// Get the documentation string + std::string getDesc() const { return mDesc; } + + /** + * Publish only selected add() methods from LLEventDispatcher. + * Every LLEventAPI add() @em must have a description string. + */ + template + void add(const std::string& name, + const std::string& desc, + CALLABLE callable, + const LLSD& required=LLSD()) + { + LLEventDispatcher::add(name, desc, callable, required); + } + + /** + * Instantiate a Response object in any LLEventAPI subclass method that + * wants to guarantee a reply (if requested) will be sent on exit from the + * method. The reply will be sent if request.has(@a replyKey), default + * "reply". If specified, the value of request[replyKey] is the name of + * the LLEventPump on which to send the reply. Conventionally you might + * code something like: + * + * @code + * void MyEventAPI::someMethod(const LLSD& request) + * { + * // Send a reply event as long as request.has("reply") + * Response response(LLSD(), request); + * // ... + * // will be sent in reply event + * response["somekey"] = some_data; + * } + * @endcode + */ + class LL_COMMON_API Response + { + public: + /** + * Instantiating a Response object in an LLEventAPI subclass method + * ensures that, if desired, a reply event will be sent. + * + * @a seed is the initial reply LLSD that will be further decorated before + * being sent as the reply + * + * @a request is the incoming request LLSD; we particularly care about + * [replyKey] and ["reqid"] + * + * @a replyKey [default "reply"] is the string name of the LLEventPump + * on which the caller wants a reply. If (! + * request.has(replyKey)), no reply will be sent. + */ + Response(const LLSD& seed, const LLSD& request, const LLSD::String& replyKey="reply"); + ~Response(); + + /** + * @code + * if (some condition) + * { + * response.warn("warnings are logged and collected in [\"warnings\"]"); + * } + * @endcode + */ + void warn(const std::string& warning); + /** + * @code + * if (some condition isn't met) + * { + * // In a function returning void, you can validly 'return + * // expression' if the expression is itself of type void. But + * // returning is up to you; response.error() has no effect on + * // flow of control. + * return response.error("error message, logged and also sent as [\"error\"]"); + * } + * @endcode + */ + void error(const std::string& error); + + /** + * set other keys... + * + * @code + * // set any attributes you want to be sent in the reply + * response["info"] = some_value; + * // ... + * response["ok"] = went_well; + * @endcode + */ + LLSD& operator[](const LLSD::String& key) { return mResp[key]; } + + /** + * set the response to the given data + */ + void setResponse(LLSD const & response){ mResp = response; } + + LLSD mResp, mReq; + LLSD::String mKey; + }; + +private: + std::string mDesc; +}; + +#endif /* ! defined(LL_LLEVENTAPI_H) */ diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp new file mode 100644 index 000000000..81cc33fbb --- /dev/null +++ b/indra/llcommon/lleventcoro.cpp @@ -0,0 +1,146 @@ +/** + * @file lleventcoro.cpp + * @author Nat Goodspeed + * @date 2009-04-29 + * @brief Implementation for lleventcoro. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lleventcoro.h" +// STL headers +#include +// std headers +// external library headers +// other Linden headers +#include "llsdserialize.h" +#include "llerror.h" +#include "llcoros.h" + +std::string LLEventDetail::listenerNameForCoroImpl(const void* self_id) +{ + // First, if this coroutine was launched by LLCoros::launch(), find that name. + std::string name(LLCoros::instance().getNameByID(self_id)); + if (! name.empty()) + { + return name; + } + // Apparently this coroutine wasn't launched by LLCoros::launch(). Check + // whether we have a memo for this self_id. + typedef std::map MapType; + static MapType memo; + MapType::const_iterator found = memo.find(self_id); + if (found != memo.end()) + { + // this coroutine instance has called us before, reuse same name + return found->second; + } + // this is the first time we've been called for this coroutine instance + name = LLEventPump::inventName("coro"); + memo[self_id] = name; + LL_INFOS("LLEventCoro") << "listenerNameForCoroImpl(" << self_id << "): inventing coro name '" + << name << "'" << LL_ENDL; + return name; +} + +void LLEventDetail::storeToLLSDPath(LLSD& dest, const LLSD& rawPath, const LLSD& value) +{ + if (rawPath.isUndefined()) + { + // no-op case + return; + } + + // Arrange to treat rawPath uniformly as an array. If it's not already an + // array, store it as the only entry in one. + LLSD path; + if (rawPath.isArray()) + { + path = rawPath; + } + else + { + path.append(rawPath); + } + + // Need to indicate a current destination -- but that current destination + // needs to change as we step through the path array. Where normally we'd + // use an LLSD& to capture a subscripted LLSD lvalue, this time we must + // instead use a pointer -- since it must be reassigned. + LLSD* pdest = &dest; + + // Now loop through that array + for (LLSD::Integer i = 0; i < path.size(); ++i) + { + if (path[i].isString()) + { + // *pdest is an LLSD map + pdest = &((*pdest)[path[i].asString()]); + } + else if (path[i].isInteger()) + { + // *pdest is an LLSD array + pdest = &((*pdest)[path[i].asInteger()]); + } + else + { + // What do we do with Real or Array or Map or ...? + // As it's a coder error -- not a user error -- rub the coder's + // face in it so it gets fixed. + LL_ERRS("lleventcoro") << "storeToLLSDPath(" << dest << ", " << rawPath << ", " << value + << "): path[" << i << "] bad type " << path[i].type() << LL_ENDL; + } + } + + // Here *pdest is where we should store value. + *pdest = value; +} + +LLSD errorException(const LLEventWithID& result, const std::string& desc) +{ + // If the result arrived on the error pump (pump 1), instead of + // returning it, deliver it via exception. + if (result.second) + { + throw LLErrorEvent(desc, result.first); + } + // That way, our caller knows a simple return must be from the reply + // pump (pump 0). + return result.first; +} + +LLSD errorLog(const LLEventWithID& result, const std::string& desc) +{ + // If the result arrived on the error pump (pump 1), log it as a fatal + // error. + if (result.second) + { + LL_ERRS("errorLog") << desc << ":" << std::endl; + LLSDSerialize::toPrettyXML(result.first, LL_CONT); + LL_CONT << LL_ENDL; + } + // A simple return must therefore be from the reply pump (pump 0). + return result.first; +} diff --git a/indra/llcommon/lleventcoro.h b/indra/llcommon/lleventcoro.h new file mode 100644 index 000000000..88a5e6ec7 --- /dev/null +++ b/indra/llcommon/lleventcoro.h @@ -0,0 +1,569 @@ +/** + * @file lleventcoro.h + * @author Nat Goodspeed + * @date 2009-04-29 + * @brief Utilities to interface between coroutines and events. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, 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$ + */ + +#if ! defined(LL_LLEVENTCORO_H) +#define LL_LLEVENTCORO_H + +#include +#include +#include +#include +#include +#include "llevents.h" +#include "llerror.h" + +/** + * Like LLListenerOrPumpName, this is a class intended for parameter lists: + * accept a const LLEventPumpOrPumpName& and you can accept either an + * LLEventPump& or its string name. For a single parameter that could + * be either, it's not hard to overload the function -- but as soon as you + * want to accept two such parameters, this is cheaper than four overloads. + */ +class LLEventPumpOrPumpName +{ +public: + /// Pass an actual LLEventPump& + LLEventPumpOrPumpName(LLEventPump& pump): + mPump(pump) + {} + /// Pass the string name of an LLEventPump + LLEventPumpOrPumpName(const std::string& pumpname): + mPump(LLEventPumps::instance().obtain(pumpname)) + {} + /// Pass string constant name of an LLEventPump. This override must be + /// explicit, since otherwise passing const char* to a function + /// accepting const LLEventPumpOrPumpName& would require two + /// different implicit conversions: const char* -> const + /// std::string& -> const LLEventPumpOrPumpName&. + LLEventPumpOrPumpName(const char* pumpname): + mPump(LLEventPumps::instance().obtain(pumpname)) + {} + /// Unspecified: "I choose not to identify an LLEventPump." + LLEventPumpOrPumpName() {} + operator LLEventPump& () const { return *mPump; } + LLEventPump& getPump() const { return *mPump; } + operator bool() const { return mPump; } + bool operator!() const { return ! mPump; } + +private: + boost::optional mPump; +}; + +/// This is an adapter for a signature like void LISTENER(const LLSD&), which +/// isn't a valid LLEventPump listener: such listeners should return bool. +template +class LLVoidListener +{ +public: + LLVoidListener(const LISTENER& listener): + mListener(listener) + {} + bool operator()(const LLSD& event) + { + mListener(event); + // don't swallow the event, let other listeners see it + return false; + } +private: + LISTENER mListener; +}; + +/// LLVoidListener helper function to infer the type of the LISTENER +template +LLVoidListener voidlistener(const LISTENER& listener) +{ + return LLVoidListener(listener); +} + +namespace LLEventDetail +{ + /** + * waitForEventOn() permits a coroutine to temporarily listen on an + * LLEventPump any number of times. We don't really want to have to ask + * the caller to label each such call with a distinct string; the whole + * point of waitForEventOn() is to present a nice sequential interface to + * the underlying LLEventPump-with-named-listeners machinery. So we'll use + * LLEventPump::inventName() to generate a distinct name for each + * temporary listener. On the other hand, because a given coroutine might + * call waitForEventOn() any number of times, we don't really want to + * consume an arbitrary number of generated inventName()s: that namespace, + * though large, is nonetheless finite. So we memoize an invented name for + * each distinct coroutine instance (each different 'self' object). We + * can't know the type of 'self', because it depends on the coroutine + * body's signature. So we cast its address to void*, looking for distinct + * pointer values. Yes, that means that an early coroutine could cache a + * value here, then be destroyed, only to be supplanted by a later + * coroutine (of the same or different type), and we'll end up + * "recognizing" the second one and reusing the listener name -- but + * that's okay, since it won't collide with any listener name used by the + * earlier coroutine since that earlier coroutine no longer exists. + */ + template + std::string listenerNameForCoro(COROUTINE_SELF& self) + { + return listenerNameForCoroImpl(self.get_id()); + } + + /// Implementation for listenerNameForCoro() + LL_COMMON_API std::string listenerNameForCoroImpl(const void* self_id); + + /** + * Implement behavior described for postAndWait()'s @a replyPumpNamePath + * parameter: + * + * * If path.isUndefined(), do nothing. + * * If path.isString(), @a dest is an LLSD map: store @a value + * into dest[path.asString()]. + * * If path.isInteger(), @a dest is an LLSD array: store @a + * value into dest[path.asInteger()]. + * * If path.isArray(), iteratively apply the rules above to step + * down through the structure of @a dest. The last array entry in @a + * path specifies the entry in the lowest-level structure in @a dest + * into which to store @a value. + * + * @note + * In the degenerate case in which @a path is an empty array, @a dest will + * @em become @a value rather than @em containing it. + */ + LL_COMMON_API void storeToLLSDPath(LLSD& dest, const LLSD& path, const LLSD& value); +} // namespace LLEventDetail + +/** + * Post specified LLSD event on the specified LLEventPump, then wait for a + * response on specified other LLEventPump. This is more than mere + * convenience: the difference between this function and the sequence + * @code + * requestPump.post(myEvent); + * LLSD reply = waitForEventOn(self, replyPump); + * @endcode + * is that the sequence above fails if the reply is posted immediately on + * @a replyPump, that is, before requestPump.post() returns. In the + * sequence above, the running coroutine isn't even listening on @a replyPump + * until requestPump.post() returns and @c waitForEventOn() is + * entered. Therefore, the coroutine completely misses an immediate reply + * event, making it wait indefinitely. + * + * By contrast, postAndWait() listens on the @a replyPump @em before posting + * the specified LLSD event on the specified @a requestPump. + * + * @param self The @c self object passed into a coroutine + * @param event LLSD data to be posted on @a requestPump + * @param requestPump an LLEventPump on which to post @a event. Pass either + * the LLEventPump& or its string name. However, if you pass a + * default-constructed @c LLEventPumpOrPumpName, we skip the post() call. + * @param replyPump an LLEventPump on which postAndWait() will listen for a + * reply. Pass either the LLEventPump& or its string name. The calling + * coroutine will wait until that reply arrives. (If you're concerned about a + * reply that might not arrive, please see also LLEventTimeout.) + * @param replyPumpNamePath specifies the location within @a event in which to + * store replyPump.getName(). This is a strictly optional convenience + * feature; obviously you can store the name in @a event "by hand" if desired. + * @a replyPumpNamePath can be specified in any of four forms: + * * @c isUndefined() (default-constructed LLSD object): do nothing. This is + * the default behavior if you omit @a replyPumpNamePath. + * * @c isInteger(): @a event is an array. Store replyPump.getName() + * in event[replyPumpNamePath.asInteger()]. + * * @c isString(): @a event is a map. Store replyPump.getName() in + * event[replyPumpNamePath.asString()]. + * * @c isArray(): @a event has several levels of structure, e.g. map of + * maps, array of arrays, array of maps, map of arrays, ... Store + * replyPump.getName() in + * event[replyPumpNamePath[0]][replyPumpNamePath[1]]... In other + * words, examine each array entry in @a replyPumpNamePath in turn. If it's an + * LLSD::String, the current level of @a event is a map; step down to + * that map entry. If it's an LLSD::Integer, the current level of @a + * event is an array; step down to that array entry. The last array entry in + * @a replyPumpNamePath specifies the entry in the lowest-level structure in + * @a event into which to store replyPump.getName(). + */ +template +LLSD postAndWait(SELF& self, const LLSD& event, const LLEventPumpOrPumpName& requestPump, + const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath=LLSD()) +{ + // declare the future + boost::coroutines::future future(self); + // make a callback that will assign a value to the future, and listen on + // the specified LLEventPump with that callback + std::string listenerName(LLEventDetail::listenerNameForCoro(self)); + LLTempBoundListener connection( + replyPump.getPump().listen(listenerName, + voidlistener(boost::coroutines::make_callback(future)))); + // skip the "post" part if requestPump is default-constructed + if (requestPump) + { + // If replyPumpNamePath is non-empty, store the replyPump name in the + // request event. + LLSD modevent(event); + LLEventDetail::storeToLLSDPath(modevent, replyPumpNamePath, replyPump.getPump().getName()); + LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << listenerName + << " posting to " << requestPump.getPump().getName() + << LL_ENDL; + + // *NOTE:Mani - Removed because modevent could contain user's hashed passwd. + // << ": " << modevent << LL_ENDL; + requestPump.getPump().post(modevent); + } + LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << listenerName + << " about to wait on LLEventPump " << replyPump.getPump().getName() + << LL_ENDL; + // trying to dereference ("resolve") the future makes us wait for it + LLSD value(*future); + LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << listenerName + << " resuming with " << value << LL_ENDL; + // returning should disconnect the connection + return value; +} + +/// Wait for the next event on the specified LLEventPump. Pass either the +/// LLEventPump& or its string name. +template +LLSD waitForEventOn(SELF& self, const LLEventPumpOrPumpName& pump) +{ + // This is now a convenience wrapper for postAndWait(). + return postAndWait(self, LLSD(), LLEventPumpOrPumpName(), pump); +} + +/// return type for two-pump variant of waitForEventOn() +typedef std::pair LLEventWithID; + +namespace LLEventDetail +{ + /** + * This helper is specifically for the two-pump version of waitForEventOn(). + * We use a single future object, but we want to listen on two pumps with it. + * Since we must still adapt from (the callable constructed by) + * boost::coroutines::make_callback() (void return) to provide an event + * listener (bool return), we've adapted LLVoidListener for the purpose. The + * basic idea is that we construct a distinct instance of WaitForEventOnHelper + * -- binding different instance data -- for each of the pumps. Then, when a + * pump delivers an LLSD value to either WaitForEventOnHelper, it can combine + * that LLSD with its discriminator to feed the future object. + */ + template + class WaitForEventOnHelper + { + public: + WaitForEventOnHelper(const LISTENER& listener, int discriminator): + mListener(listener), + mDiscrim(discriminator) + {} + // this signature is required for an LLEventPump listener + bool operator()(const LLSD& event) + { + // our future object is defined to accept LLEventWithID + mListener(LLEventWithID(event, mDiscrim)); + // don't swallow the event, let other listeners see it + return false; + } + private: + LISTENER mListener; + const int mDiscrim; + }; + + /// WaitForEventOnHelper type-inference helper + template + WaitForEventOnHelper wfeoh(const LISTENER& listener, int discriminator) + { + return WaitForEventOnHelper(listener, discriminator); + } +} // namespace LLEventDetail + +/** + * This function waits for a reply on either of two specified LLEventPumps. + * Otherwise, it closely resembles postAndWait(); please see the documentation + * for that function for detailed parameter info. + * + * While we could have implemented the single-pump variant in terms of this + * one, there's enough added complexity here to make it worthwhile to give the + * single-pump variant its own straightforward implementation. Conversely, + * though we could use preprocessor logic to generate n-pump overloads up to + * BOOST_COROUTINE_WAIT_MAX, we don't foresee a use case. This two-pump + * overload exists because certain event APIs are defined in terms of a reply + * LLEventPump and an error LLEventPump. + * + * The LLEventWithID return value provides not only the received event, but + * the index of the pump on which it arrived (0 or 1). + * + * @note + * I'd have preferred to overload the name postAndWait() for both signatures. + * But consider the following ambiguous call: + * @code + * postAndWait(self, LLSD(), requestPump, replyPump, "someString"); + * @endcode + * "someString" could be converted to either LLSD (@a replyPumpNamePath for + * the single-pump function) or LLEventOrPumpName (@a replyPump1 for two-pump + * function). + * + * It seems less burdensome to write postAndWait2() than to write either + * LLSD("someString") or LLEventOrPumpName("someString"). + */ +template +LLEventWithID postAndWait2(SELF& self, const LLSD& event, + const LLEventPumpOrPumpName& requestPump, + const LLEventPumpOrPumpName& replyPump0, + const LLEventPumpOrPumpName& replyPump1, + const LLSD& replyPump0NamePath=LLSD(), + const LLSD& replyPump1NamePath=LLSD()) +{ + // declare the future + boost::coroutines::future future(self); + // either callback will assign a value to this future; listen on + // each specified LLEventPump with a callback + std::string name(LLEventDetail::listenerNameForCoro(self)); + LLTempBoundListener connection0( + replyPump0.getPump().listen(name + "a", + LLEventDetail::wfeoh(boost::coroutines::make_callback(future), 0))); + LLTempBoundListener connection1( + replyPump1.getPump().listen(name + "b", + LLEventDetail::wfeoh(boost::coroutines::make_callback(future), 1))); + // skip the "post" part if requestPump is default-constructed + if (requestPump) + { + // If either replyPumpNamePath is non-empty, store the corresponding + // replyPump name in the request event. + LLSD modevent(event); + LLEventDetail::storeToLLSDPath(modevent, replyPump0NamePath, + replyPump0.getPump().getName()); + LLEventDetail::storeToLLSDPath(modevent, replyPump1NamePath, + replyPump1.getPump().getName()); + LL_DEBUGS("lleventcoro") << "postAndWait2(): coroutine " << name + << " posting to " << requestPump.getPump().getName() + << ": " << modevent << LL_ENDL; + requestPump.getPump().post(modevent); + } + LL_DEBUGS("lleventcoro") << "postAndWait2(): coroutine " << name + << " about to wait on LLEventPumps " << replyPump0.getPump().getName() + << ", " << replyPump1.getPump().getName() << LL_ENDL; + // trying to dereference ("resolve") the future makes us wait for it + LLEventWithID value(*future); + LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << name + << " resuming with (" << value.first << ", " << value.second << ")" + << LL_ENDL; + // returning should disconnect both connections + return value; +} + +/** + * Wait for the next event on either of two specified LLEventPumps. + */ +template +LLEventWithID +waitForEventOn(SELF& self, + const LLEventPumpOrPumpName& pump0, const LLEventPumpOrPumpName& pump1) +{ + // This is now a convenience wrapper for postAndWait2(). + return postAndWait2(self, LLSD(), LLEventPumpOrPumpName(), pump0, pump1); +} + +/** + * Helper for the two-pump variant of waitForEventOn(), e.g.: + * + * @code + * LLSD reply = errorException(waitForEventOn(self, replyPump, errorPump), + * "error response from login.cgi"); + * @endcode + * + * Examines an LLEventWithID, assuming that the second pump (pump 1) is + * listening for an error indication. If the incoming data arrived on pump 1, + * throw an LLErrorEvent exception. If the incoming data arrived on pump 0, + * just return it. Since a normal return can only be from pump 0, we no longer + * need the LLEventWithID's discriminator int; we can just return the LLSD. + * + * @note I'm not worried about introducing the (fairly generic) name + * errorException() into global namespace, because how many other overloads of + * the same name are going to accept an LLEventWithID parameter? + */ +LLSD errorException(const LLEventWithID& result, const std::string& desc); + +/** + * Exception thrown by errorException(). We don't call this LLEventError + * because it's not an error in event processing: rather, this exception + * announces an event that bears error information (for some other API). + */ +class LL_COMMON_API LLErrorEvent: public std::runtime_error +{ +public: + LLErrorEvent(const std::string& what, const LLSD& data): + std::runtime_error(what), + mData(data) + {} + virtual ~LLErrorEvent() throw() {} + + LLSD getData() const { return mData; } + +private: + LLSD mData; +}; + +/** + * Like errorException(), save that this trips a fatal error using LL_ERRS + * rather than throwing an exception. + */ +LL_COMMON_API LLSD errorLog(const LLEventWithID& result, const std::string& desc); + +/** + * Certain event APIs require the name of an LLEventPump on which they should + * post results. While it works to invent a distinct name and let + * LLEventPumps::obtain() instantiate the LLEventPump as a "named singleton," + * in a certain sense it's more robust to instantiate a local LLEventPump and + * provide its name instead. This class packages the following idiom: + * + * 1. Instantiate a local LLCoroEventPump, with an optional name prefix. + * 2. Provide its actual name to the event API in question as the name of the + * reply LLEventPump. + * 3. Initiate the request to the event API. + * 4. Call your LLEventTempStream's wait() method to wait for the reply. + * 5. Let the LLCoroEventPump go out of scope. + */ +class LL_COMMON_API LLCoroEventPump +{ +public: + LLCoroEventPump(const std::string& name="coro"): + mPump(name, true) // allow tweaking the pump instance name + {} + /// It's typical to request the LLEventPump name to direct an event API to + /// send its response to this pump. + std::string getName() const { return mPump.getName(); } + /// Less typically, we'd request the pump itself for some reason. + LLEventPump& getPump() { return mPump; } + + /** + * Wait for an event on this LLEventPump. + * + * @note + * The other major usage pattern we considered was to bind @c self at + * LLCoroEventPump construction time, which would avoid passing the + * parameter to each wait() call. But if we were going to bind @c self as + * a class member, we'd need to specify a class template parameter + * indicating its type. The big advantage of passing it to the wait() call + * is that the type can be implicit. + */ + template + LLSD wait(SELF& self) + { + return waitForEventOn(self, mPump); + } + + template + LLSD postAndWait(SELF& self, const LLSD& event, const LLEventPumpOrPumpName& requestPump, + const LLSD& replyPumpNamePath=LLSD()) + { + return ::postAndWait(self, event, requestPump, mPump, replyPumpNamePath); + } + +private: + LLEventStream mPump; +}; + +/** + * Other event APIs require the names of two different LLEventPumps: one for + * success response, the other for error response. Extend LLCoroEventPump + * for the two-pump use case. + */ +class LL_COMMON_API LLCoroEventPumps +{ +public: + LLCoroEventPumps(const std::string& name="coro", + const std::string& suff0="Reply", + const std::string& suff1="Error"): + mPump0(name + suff0, true), // allow tweaking the pump instance name + mPump1(name + suff1, true) + {} + /// request pump 0's name + std::string getName0() const { return mPump0.getName(); } + /// request pump 1's name + std::string getName1() const { return mPump1.getName(); } + /// request both names + std::pair getNames() const + { + return std::pair(mPump0.getName(), mPump1.getName()); + } + + /// request pump 0 + LLEventPump& getPump0() { return mPump0; } + /// request pump 1 + LLEventPump& getPump1() { return mPump1; } + + /// waitForEventOn(self, either of our two LLEventPumps) + template + LLEventWithID wait(SELF& self) + { + return waitForEventOn(self, mPump0, mPump1); + } + + /// errorException(wait(self)) + template + LLSD waitWithException(SELF& self) + { + return errorException(wait(self), std::string("Error event on ") + getName1()); + } + + /// errorLog(wait(self)) + template + LLSD waitWithLog(SELF& self) + { + return errorLog(wait(self), std::string("Error event on ") + getName1()); + } + + template + LLEventWithID postAndWait(SELF& self, const LLSD& event, + const LLEventPumpOrPumpName& requestPump, + const LLSD& replyPump0NamePath=LLSD(), + const LLSD& replyPump1NamePath=LLSD()) + { + return postAndWait2(self, event, requestPump, mPump0, mPump1, + replyPump0NamePath, replyPump1NamePath); + } + + template + LLSD postAndWaitWithException(SELF& self, const LLSD& event, + const LLEventPumpOrPumpName& requestPump, + const LLSD& replyPump0NamePath=LLSD(), + const LLSD& replyPump1NamePath=LLSD()) + { + return errorException(postAndWait(self, event, requestPump, + replyPump0NamePath, replyPump1NamePath), + std::string("Error event on ") + getName1()); + } + + template + LLSD postAndWaitWithLog(SELF& self, const LLSD& event, + const LLEventPumpOrPumpName& requestPump, + const LLSD& replyPump0NamePath=LLSD(), + const LLSD& replyPump1NamePath=LLSD()) + { + return errorLog(postAndWait(self, event, requestPump, + replyPump0NamePath, replyPump1NamePath), + std::string("Error event on ") + getName1()); + } + +private: + LLEventStream mPump0, mPump1; +}; + +#endif /* ! defined(LL_LLEVENTCORO_H) */ diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp new file mode 100644 index 000000000..5b6d4efbe --- /dev/null +++ b/indra/llcommon/lleventdispatcher.cpp @@ -0,0 +1,672 @@ +/** + * @file lleventdispatcher.cpp + * @author Nat Goodspeed + * @date 2009-06-18 + * @brief Implementation for lleventdispatcher. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, 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$ + */ + +#if LL_WINDOWS +#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally +#endif + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lleventdispatcher.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "llevents.h" +#include "llerror.h" +#include "llsdutil.h" +#include "stringize.h" +#include // std::auto_ptr + +/***************************************************************************** +* LLSDArgsSource +*****************************************************************************/ +/** + * Store an LLSD array, producing its elements one at a time. Die with LL_ERRS + * if the consumer requests more elements than the array contains. + */ +class LL_COMMON_API LLSDArgsSource +{ +public: + LLSDArgsSource(const std::string function, const LLSD& args); + ~LLSDArgsSource(); + + LLSD next(); + + void done() const; + +private: + std::string _function; + LLSD _args; + LLSD::Integer _index; +}; + +LLSDArgsSource::LLSDArgsSource(const std::string function, const LLSD& args): + _function(function), + _args(args), + _index(0) +{ + if (! (_args.isUndefined() || _args.isArray())) + { + LL_ERRS("LLSDArgsSource") << _function << " needs an args array instead of " + << _args << LL_ENDL; + } +} + +LLSDArgsSource::~LLSDArgsSource() +{ + done(); +} + +LLSD LLSDArgsSource::next() +{ + if (_index >= _args.size()) + { + LL_ERRS("LLSDArgsSource") << _function << " requires more arguments than the " + << _args.size() << " provided: " << _args << LL_ENDL; + } + return _args[_index++]; +} + +void LLSDArgsSource::done() const +{ + if (_index < _args.size()) + { + LL_WARNS("LLSDArgsSource") << _function << " only consumed " << _index + << " of the " << _args.size() << " arguments provided: " + << _args << LL_ENDL; + } +} + +/***************************************************************************** +* LLSDArgsMapper +*****************************************************************************/ +/** + * From a formal parameters description and a map of arguments, construct an + * arguments array. + * + * That is, given: + * - an LLSD array of length n containing parameter-name strings, + * corresponding to the arguments of a function of interest + * - an LLSD collection specifying default parameter values, either: + * - an LLSD array of length m <= n, matching the rightmost m params, or + * - an LLSD map explicitly stating default name=value pairs + * - an LLSD map of parameter names and actual values for a particular + * function call + * construct an LLSD array of actual argument values for this function call. + * + * The parameter-names array and the defaults collection describe the function + * being called. The map might vary with every call, providing argument values + * for the described parameters. + * + * The array of parameter names must match the number of parameters expected + * by the function of interest. + * + * If you pass a map of default parameter values, it provides default values + * as you might expect. It is an error to specify a default value for a name + * not listed in the parameters array. + * + * If you pass an array of default parameter values, it is mapped to the + * rightmost m of the n parameter names. It is an error if the default-values + * array is longer than the parameter-names array. Consider the following + * parameter names: ["a", "b", "c", "d"]. + * + * - An empty array of default values (or an isUndefined() value) asserts that + * every one of the above parameter names is required. + * - An array of four default values [1, 2, 3, 4] asserts that every one of + * the above parameters is optional. If the current parameter map is empty, + * they will be passed to the function as [1, 2, 3, 4]. + * - An array of two default values [11, 12] asserts that parameters "a" and + * "b" are required, while "c" and "d" are optional, having default values + * "c"=11 and "d"=12. + * + * The arguments array is constructed as follows: + * + * - Arguments-map keys not found in the parameter-names array are ignored. + * - Entries from the map provide values for an improper subset of the + * parameters named in the parameter-names array. This results in a + * tentative values array with "holes." (size of map) + (number of holes) = + * (size of names array) + * - Holes are filled with the default values. + * - Any remaining holes constitute an error. + */ +class LL_COMMON_API LLSDArgsMapper +{ +public: + /// Accept description of function: function name, param names, param + /// default values + LLSDArgsMapper(const std::string& function, const LLSD& names, const LLSD& defaults); + + /// Given arguments map, return LLSD::Array of parameter values, or LL_ERRS. + LLSD map(const LLSD& argsmap) const; + +private: + static std::string formatlist(const LLSD&); + + // The function-name string is purely descriptive. We want error messages + // to be able to indicate which function's LLSDArgsMapper has the problem. + std::string _function; + // Store the names array pretty much as given. + LLSD _names; + // Though we're handed an array of name strings, it's more useful to us to + // store it as a map from name string to position index. Of course that's + // easy to generate from the incoming names array, but why do it more than + // once? + typedef std::map IndexMap; + IndexMap _indexes; + // Generated array of default values, aligned with the array of param names. + LLSD _defaults; + // Indicate whether we have a default value for each param. + typedef std::vector FilledVector; + FilledVector _has_dft; +}; + +LLSDArgsMapper::LLSDArgsMapper(const std::string& function, + const LLSD& names, const LLSD& defaults): + _function(function), + _names(names), + _has_dft(names.size()) +{ + if (! (_names.isUndefined() || _names.isArray())) + { + LL_ERRS("LLSDArgsMapper") << function << " names must be an array, not " << names << LL_ENDL; + } + LLSD::Integer nparams(_names.size()); + // From _names generate _indexes. + for (LLSD::Integer ni = 0, nend = _names.size(); ni < nend; ++ni) + { + _indexes[_names[ni]] = ni; + } + + // Presize _defaults() array so we don't have to resize it more than once. + // All entries are initialized to LLSD(); but since _has_dft is still all + // 0, they're all "holes" for now. + if (nparams) + { + _defaults[nparams - 1] = LLSD(); + } + + if (defaults.isUndefined() || defaults.isArray()) + { + LLSD::Integer ndefaults = defaults.size(); + // defaults is a (possibly empty) array. Right-align it with names. + if (ndefaults > nparams) + { + LL_ERRS("LLSDArgsMapper") << function << " names array " << names + << " shorter than defaults array " << defaults << LL_ENDL; + } + + // Offset by which we slide defaults array right to right-align with + // _names array + LLSD::Integer offset = nparams - ndefaults; + // Fill rightmost _defaults entries from defaults, and mark them as + // filled + for (LLSD::Integer i = 0, iend = ndefaults; i < iend; ++i) + { + _defaults[i + offset] = defaults[i]; + _has_dft[i + offset] = 1; + } + } + else if (defaults.isMap()) + { + // defaults is a map. Use it to populate the _defaults array. + LLSD bogus; + for (LLSD::map_const_iterator mi(defaults.beginMap()), mend(defaults.endMap()); + mi != mend; ++mi) + { + IndexMap::const_iterator ixit(_indexes.find(mi->first)); + if (ixit == _indexes.end()) + { + bogus.append(mi->first); + continue; + } + + LLSD::Integer pos = ixit->second; + // Store default value at that position in the _defaults array. + _defaults[pos] = mi->second; + // Don't forget to record the fact that we've filled this + // position. + _has_dft[pos] = 1; + } + if (bogus.size()) + { + LL_ERRS("LLSDArgsMapper") << function << " defaults specified for nonexistent params " + << formatlist(bogus) << LL_ENDL; + } + } + else + { + LL_ERRS("LLSDArgsMapper") << function << " defaults must be a map or an array, not " + << defaults << LL_ENDL; + } +} + +LLSD LLSDArgsMapper::map(const LLSD& argsmap) const +{ + if (! (argsmap.isUndefined() || argsmap.isMap() || argsmap.isArray())) + { + LL_ERRS("LLSDArgsMapper") << _function << " map() needs a map or array, not " + << argsmap << LL_ENDL; + } + // Initialize the args array. Indexing a non-const LLSD array grows it + // to appropriate size, but we don't want to resize this one on each + // new operation. Just make it as big as we need before we start + // stuffing values into it. + LLSD args(LLSD::emptyArray()); + if (_defaults.size() == 0) + { + // If this function requires no arguments, fast exit. (Don't try to + // assign to args[-1].) + return args; + } + args[_defaults.size() - 1] = LLSD(); + + // Get a vector of chars to indicate holes. It's tempting to just scan + // for LLSD::isUndefined() values after filling the args array from + // the map, but it's plausible for caller to explicitly pass + // isUndefined() as the value of some parameter name. That's legal + // since isUndefined() has well-defined conversions (default value) + // for LLSD data types. So use a whole separate array for detecting + // holes. (Avoid std::vector which is known to be odd -- can we + // iterate?) + FilledVector filled(args.size()); + + if (argsmap.isArray()) + { + // Fill args from array. If there are too many args in passed array, + // ignore the rest. + LLSD::Integer size(argsmap.size()); + if (size > args.size()) + { + // We don't just use std::min() because we want to sneak in this + // warning if caller passes too many args. + LL_WARNS("LLSDArgsMapper") << _function << " needs " << args.size() + << " params, ignoring last " << (size - args.size()) + << " of passed " << size << ": " << argsmap << LL_ENDL; + size = args.size(); + } + for (LLSD::Integer i(0); i < size; ++i) + { + // Copy the actual argument from argsmap + args[i] = argsmap[i]; + // Note that it's been filled + filled[i] = 1; + } + } + else + { + // argsmap is in fact a map. Walk the map. + for (LLSD::map_const_iterator mi(argsmap.beginMap()), mend(argsmap.endMap()); + mi != mend; ++mi) + { + // mi->first is a parameter-name string, with mi->second its + // value. Look up the name's position index in _indexes. + IndexMap::const_iterator ixit(_indexes.find(mi->first)); + if (ixit == _indexes.end()) + { + // Allow for a map containing more params than were passed in + // our names array. Caller typically receives a map containing + // the function name, cruft such as reqid, etc. Ignore keys + // not defined in _indexes. + LL_DEBUGS("LLSDArgsMapper") << _function << " ignoring " + << mi->first << "=" << mi->second << LL_ENDL; + continue; + } + LLSD::Integer pos = ixit->second; + // Store the value at that position in the args array. + args[pos] = mi->second; + // Don't forget to record the fact that we've filled this + // position. + filled[pos] = 1; + } + } + + // Fill any remaining holes from _defaults. + LLSD unfilled(LLSD::emptyArray()); + for (LLSD::Integer i = 0, iend = args.size(); i < iend; ++i) + { + if (! filled[i]) + { + // If there's no default value for this parameter, that's an + // error. + if (! _has_dft[i]) + { + unfilled.append(_names[i]); + } + else + { + args[i] = _defaults[i]; + } + } + } + // If any required args -- args without defaults -- were left unfilled + // by argsmap, that's a problem. + if (unfilled.size()) + { + LL_ERRS("LLSDArgsMapper") << _function << " missing required arguments " + << formatlist(unfilled) << " from " << argsmap << LL_ENDL; + } + + // done + return args; +} + +std::string LLSDArgsMapper::formatlist(const LLSD& list) +{ + std::ostringstream out; + const char* delim = ""; + for (LLSD::array_const_iterator li(list.beginArray()), lend(list.endArray()); + li != lend; ++li) + { + out << delim << li->asString(); + delim = ", "; + } + return out.str(); +} + +LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key): + mDesc(desc), + mKey(key) +{ +} + +LLEventDispatcher::~LLEventDispatcher() +{ +} + +/** + * DispatchEntry subclass used for callables accepting(const LLSD&) + */ +struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchEntry +{ + LLSDDispatchEntry(const std::string& desc, const Callable& func, const LLSD& required): + DispatchEntry(desc), + mFunc(func), + mRequired(required) + {} + + Callable mFunc; + LLSD mRequired; + + virtual void call(const std::string& desc, const LLSD& event) const + { + // Validate the syntax of the event itself. + std::string mismatch(llsd_matches(mRequired, event)); + if (! mismatch.empty()) + { + LL_ERRS("LLEventDispatcher") << desc << ": bad request: " << mismatch << LL_ENDL; + } + // Event syntax looks good, go for it! + mFunc(event); + } + + virtual LLSD addMetadata(LLSD meta) const + { + meta["required"] = mRequired; + return meta; + } +}; + +/** + * DispatchEntry subclass for passing LLSD to functions accepting + * arbitrary argument types (convertible via LLSDParam) + */ +struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::DispatchEntry +{ + ParamsDispatchEntry(const std::string& desc, const invoker_function& func): + DispatchEntry(desc), + mInvoker(func) + {} + + invoker_function mInvoker; + + virtual void call(const std::string& desc, const LLSD& event) const + { + LLSDArgsSource src(desc, event); + mInvoker(boost::bind(&LLSDArgsSource::next, boost::ref(src))); + } +}; + +/** + * DispatchEntry subclass for dispatching LLSD::Array to functions accepting + * arbitrary argument types (convertible via LLSDParam) + */ +struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry +{ + ArrayParamsDispatchEntry(const std::string& desc, const invoker_function& func, + LLSD::Integer arity): + ParamsDispatchEntry(desc, func), + mArity(arity) + {} + + LLSD::Integer mArity; + + virtual LLSD addMetadata(LLSD meta) const + { + LLSD array(LLSD::emptyArray()); + // Resize to number of arguments required + if (mArity) + array[mArity - 1] = LLSD(); + llassert_always(array.size() == mArity); + meta["required"] = array; + return meta; + } +}; + +/** + * DispatchEntry subclass for dispatching LLSD::Map to functions accepting + * arbitrary argument types (convertible via LLSDParam) + */ +struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry +{ + MapParamsDispatchEntry(const std::string& name, const std::string& desc, + const invoker_function& func, + const LLSD& params, const LLSD& defaults): + ParamsDispatchEntry(desc, func), + mMapper(name, params, defaults), + mRequired(LLSD::emptyMap()) + { + // Build the set of all param keys, then delete the ones that are + // optional. What's left are the ones that are required. + for (LLSD::array_const_iterator pi(params.beginArray()), pend(params.endArray()); + pi != pend; ++pi) + { + mRequired[pi->asString()] = LLSD(); + } + + if (defaults.isArray() || defaults.isUndefined()) + { + // Right-align the params and defaults arrays. + LLSD::Integer offset = params.size() - defaults.size(); + // Now the name of every defaults[i] is at params[i + offset]. + for (LLSD::Integer i(0), iend(defaults.size()); i < iend; ++i) + { + // Erase this optional param from mRequired. + mRequired.erase(params[i + offset].asString()); + // Instead, make an entry in mOptional with the default + // param's name and value. + mOptional[params[i + offset].asString()] = defaults[i]; + } + } + else if (defaults.isMap()) + { + // if defaults is already a map, then it's already in the form we + // intend to deliver in metadata + mOptional = defaults; + // Just delete from mRequired every key appearing in mOptional. + for (LLSD::map_const_iterator mi(mOptional.beginMap()), mend(mOptional.endMap()); + mi != mend; ++mi) + { + mRequired.erase(mi->first); + } + } + } + + LLSDArgsMapper mMapper; + LLSD mRequired; + LLSD mOptional; + + virtual void call(const std::string& desc, const LLSD& event) const + { + // Just convert from LLSD::Map to LLSD::Array using mMapper, then pass + // to base-class call() method. + ParamsDispatchEntry::call(desc, mMapper.map(event)); + } + + virtual LLSD addMetadata(LLSD meta) const + { + meta["required"] = mRequired; + meta["optional"] = mOptional; + return meta; + } +}; + +void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name, + const std::string& desc, + const invoker_function& invoker, + LLSD::Integer arity) +{ + mDispatch.insert( + DispatchMap::value_type(name, DispatchMap::mapped_type( + new ArrayParamsDispatchEntry(desc, invoker, arity)))); +} + +void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, + const std::string& desc, + const invoker_function& invoker, + const LLSD& params, + const LLSD& defaults) +{ + mDispatch.insert( + DispatchMap::value_type(name, DispatchMap::mapped_type( + new MapParamsDispatchEntry(name, desc, invoker, params, defaults)))); +} + +/// Register a callable by name +void LLEventDispatcher::add(const std::string& name, const std::string& desc, + const Callable& callable, const LLSD& required) +{ + mDispatch.insert( + DispatchMap::value_type(name, DispatchMap::mapped_type( + new LLSDDispatchEntry(desc, callable, required)))); +} + +void LLEventDispatcher::addFail(const std::string& name, const std::string& classname) const +{ + LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ")::add(" << name + << "): " << classname << " is not a subclass " + << "of LLEventDispatcher" << LL_ENDL; +} + +/// Unregister a callable +bool LLEventDispatcher::remove(const std::string& name) +{ + DispatchMap::iterator found = mDispatch.find(name); + if (found == mDispatch.end()) + { + return false; + } + mDispatch.erase(found); + return true; +} + +/// Call a registered callable with an explicitly-specified name. If no +/// such callable exists, die with LL_ERRS. +void LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const +{ + if (! try_call(name, event)) + { + LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): '" << name + << "' not found" << LL_ENDL; + } +} + +/// Extract the @a key value from the incoming @a event, and call the +/// callable whose name is specified by that map @a key. If no such +/// callable exists, die with LL_ERRS. +void LLEventDispatcher::operator()(const LLSD& event) const +{ + // This could/should be implemented in terms of the two-arg overload. + // However -- we can produce a more informative error message. + std::string name(event[mKey]); + if (! try_call(name, event)) + { + LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): bad " << mKey + << " value '" << name << "'" << LL_ENDL; + } +} + +bool LLEventDispatcher::try_call(const LLSD& event) const +{ + return try_call(event[mKey], event); +} + +bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const +{ + DispatchMap::const_iterator found = mDispatch.find(name); + if (found == mDispatch.end()) + { + return false; + } + // Found the name, so it's plausible to even attempt the call. + found->second->call(STRINGIZE("LLEventDispatcher(" << mDesc << ") calling '" << name << "'"), + event); + return true; // tell caller we were able to call +} + +LLSD LLEventDispatcher::getMetadata(const std::string& name) const +{ + DispatchMap::const_iterator found = mDispatch.find(name); + if (found == mDispatch.end()) + { + return LLSD(); + } + LLSD meta; + meta["name"] = name; + meta["desc"] = found->second->mDesc; + return found->second->addMetadata(meta); +} + +LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key): + LLEventDispatcher(pumpname, key), + mPump(pumpname, true), // allow tweaking for uniqueness + mBoundListener(mPump.listen("self", boost::bind(&LLDispatchListener::process, this, _1))) +{ +} + +bool LLDispatchListener::process(const LLSD& event) +{ + (*this)(event); + return false; +} + +LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc): + mDesc(desc) +{} + diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h new file mode 100644 index 000000000..7acc61de4 --- /dev/null +++ b/indra/llcommon/lleventdispatcher.h @@ -0,0 +1,541 @@ +/** + * @file lleventdispatcher.h + * @author Nat Goodspeed + * @date 2009-06-18 + * @brief Central mechanism for dispatching events by string name. This is + * useful when you have a single LLEventPump listener on which you can + * request different operations, vs. instantiating a different + * LLEventPump for each such operation. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, 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$ + * + * The invoker machinery that constructs a boost::fusion argument list for use + * with boost::fusion::invoke() is derived from + * http://www.boost.org/doc/libs/1_45_0/libs/function_types/example/interpreter.hpp + * whose license information is copied below: + * + * "(C) Copyright Tobias Schwinger + * + * Use modification and distribution are subject to the boost Software License, + * Version 1.0. (See http://www.boost.org/LICENSE_1_0.txt)." + */ + +#if ! defined(LL_LLEVENTDISPATCHER_H) +#define LL_LLEVENTDISPATCHER_H + +// nil is too generic a term to be allowed to be a global macro. In +// particular, boost::fusion defines a 'class nil' (properly encapsulated in a +// namespace) that a global 'nil' macro breaks badly. +#if defined(nil) +// Capture the value of the macro 'nil', hoping int is an appropriate type. +static const int nil_(nil); +// Now forget the macro. +#undef nil +// Finally, reintroduce 'nil' as a properly-scoped alias for the previously- +// defined const 'nil_'. Make it static since otherwise it produces duplicate- +// symbol link errors later. +static const int& nil(nil_); +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "llevents.h" +#include "llsdutil.h" + +class LLSD; + +/** + * Given an LLSD map, examine a string-valued key and call a corresponding + * callable. This class is designed to be contained by an LLEventPump + * listener class that will register some of its own methods, though any + * callable can be used. + */ +class LL_COMMON_API LLEventDispatcher +{ +public: + LLEventDispatcher(const std::string& desc, const std::string& key); + virtual ~LLEventDispatcher(); + + /// @name Register functions accepting(const LLSD&) + //@{ + + /// Accept any C++ callable with the right signature, typically a + /// boost::bind() expression + typedef boost::function Callable; + + /** + * Register a @a callable by @a name. The passed @a callable accepts a + * single LLSD value and uses it in any way desired, e.g. extract + * parameters and call some other function. The optional @a required + * parameter is used to validate the structure of each incoming event (see + * llsd_matches()). + */ + void add(const std::string& name, + const std::string& desc, + const Callable& callable, + const LLSD& required=LLSD()); + + /** + * The case of a free function (or static method) accepting(const LLSD&) + * could also be intercepted by the arbitrary-args overload below. Ensure + * that it's directed to the Callable overload above instead. + */ + void add(const std::string& name, + const std::string& desc, + void (*f)(const LLSD&), + const LLSD& required=LLSD()) + { + add(name, desc, Callable(f), required); + } + + /** + * Special case: a subclass of this class can pass an unbound member + * function pointer (of an LLEventDispatcher subclass) without explicitly + * specifying the boost::bind() expression. The passed @a method + * accepts a single LLSD value, presumably containing other parameters. + */ + template + void add(const std::string& name, + const std::string& desc, + void (CLASS::*method)(const LLSD&), + const LLSD& required=LLSD()) + { + addMethod(name, desc, method, required); + } + + /// Overload for both const and non-const methods. The passed @a method + /// accepts a single LLSD value, presumably containing other parameters. + template + void add(const std::string& name, + const std::string& desc, + void (CLASS::*method)(const LLSD&) const, + const LLSD& required=LLSD()) + { + addMethod(name, desc, method, required); + } + + //@} + + /// @name Register functions with arbitrary param lists + //@{ + + /** + * Register a free function with arbitrary parameters. (This also works + * for static class methods.) + * + * @note This supports functions with up to about 6 parameters -- after + * that you start getting dismaying compile errors in which + * boost::fusion::joint_view is mentioned a surprising number of times. + * + * When calling this name, pass an LLSD::Array. Each entry in turn will be + * converted to the corresponding parameter type using LLSDParam. + */ + template + typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin + >::type add(const std::string& name, + const std::string& desc, + Function f); + + /** + * Register a nonstatic class method with arbitrary parameters. + * + * @note This supports functions with up to about 6 parameters -- after + * that you start getting dismaying compile errors in which + * boost::fusion::joint_view is mentioned a surprising number of times. + * + * To cover cases such as a method on an LLSingleton we don't yet want to + * instantiate, instead of directly storing an instance pointer, accept a + * nullary callable returning a pointer/reference to the desired class + * instance. If you already have an instance in hand, + * boost::lambda::var(instance) or boost::lambda::constant(instance_ptr) + * produce suitable callables. + * + * When calling this name, pass an LLSD::Array. Each entry in turn will be + * converted to the corresponding parameter type using LLSDParam. + */ + template + typename boost::enable_if< boost::function_types::is_member_function_pointer + >::type add(const std::string& name, + const std::string& desc, + Method f, + const InstanceGetter& getter); + + /** + * Register a free function with arbitrary parameters. (This also works + * for static class methods.) + * + * @note This supports functions with up to about 6 parameters -- after + * that you start getting dismaying compile errors in which + * boost::fusion::joint_view is mentioned a surprising number of times. + * + * Pass an LLSD::Array of parameter names, and optionally another + * LLSD::Array of default parameter values, a la LLSDArgsMapper. + * + * When calling this name, pass an LLSD::Map. We will internally generate + * an LLSD::Array using LLSDArgsMapper and then convert each entry in turn + * to the corresponding parameter type using LLSDParam. + */ + template + typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin + >::type add(const std::string& name, + const std::string& desc, + Function f, + const LLSD& params, + const LLSD& defaults=LLSD()); + + /** + * Register a nonstatic class method with arbitrary parameters. + * + * @note This supports functions with up to about 6 parameters -- after + * that you start getting dismaying compile errors in which + * boost::fusion::joint_view is mentioned a surprising number of times. + * + * To cover cases such as a method on an LLSingleton we don't yet want to + * instantiate, instead of directly storing an instance pointer, accept a + * nullary callable returning a pointer/reference to the desired class + * instance. If you already have an instance in hand, + * boost::lambda::var(instance) or boost::lambda::constant(instance_ptr) + * produce suitable callables. + * + * Pass an LLSD::Array of parameter names, and optionally another + * LLSD::Array of default parameter values, a la LLSDArgsMapper. + * + * When calling this name, pass an LLSD::Map. We will internally generate + * an LLSD::Array using LLSDArgsMapper and then convert each entry in turn + * to the corresponding parameter type using LLSDParam. + */ + template + typename boost::enable_if< boost::function_types::is_member_function_pointer + >::type add(const std::string& name, + const std::string& desc, + Method f, + const InstanceGetter& getter, + const LLSD& params, + const LLSD& defaults=LLSD()); + + //@} + + /// Unregister a callable + bool remove(const std::string& name); + + /// Call a registered callable with an explicitly-specified name. If no + /// such callable exists, die with LL_ERRS. If the @a event fails to match + /// the @a required prototype specified at add() time, die with LL_ERRS. + void operator()(const std::string& name, const LLSD& event) const; + + /// Call a registered callable with an explicitly-specified name and + /// return true. If no such callable exists, return + /// false. If the @a event fails to match the @a required + /// prototype specified at add() time, die with LL_ERRS. + bool try_call(const std::string& name, const LLSD& event) const; + + /// Extract the @a key value from the incoming @a event, and call the + /// callable whose name is specified by that map @a key. If no such + /// callable exists, die with LL_ERRS. If the @a event fails to match the + /// @a required prototype specified at add() time, die with LL_ERRS. + void operator()(const LLSD& event) const; + + /// Extract the @a key value from the incoming @a event, call the callable + /// whose name is specified by that map @a key and return true. + /// If no such callable exists, return false. If the @a event + /// fails to match the @a required prototype specified at add() time, die + /// with LL_ERRS. + bool try_call(const LLSD& event) const; + + /// @name Iterate over defined names + //@{ + typedef std::pair NameDesc; + +private: + struct DispatchEntry + { + DispatchEntry(const std::string& desc); + virtual ~DispatchEntry() {} // suppress MSVC warning, sigh + + std::string mDesc; + + virtual void call(const std::string& desc, const LLSD& event) const = 0; + virtual LLSD addMetadata(LLSD) const = 0; + }; + // Tried using boost::ptr_map, but ptr_map<> + // wants its value type to be "clonable," even just to dereference an + // iterator. I don't want to clone entries -- if I have to copy an entry + // around, I want it to continue pointing to the same DispatchEntry + // subclass object. However, I definitely want DispatchMap to destroy + // DispatchEntry if no references are outstanding at the time an entry is + // removed. This looks like a job for boost::shared_ptr. + typedef std::map > DispatchMap; + +public: + /// We want the flexibility to redefine what data we store per name, + /// therefore our public interface doesn't expose DispatchMap iterators, + /// or DispatchMap itself, or DispatchEntry. Instead we explicitly + /// transform each DispatchMap item to NameDesc on dereferencing. + typedef boost::transform_iterator const_iterator; + const_iterator begin() const + { + return boost::make_transform_iterator(mDispatch.begin(), makeNameDesc); + } + const_iterator end() const + { + return boost::make_transform_iterator(mDispatch.end(), makeNameDesc); + } + //@} + + /// Get information about a specific Callable + LLSD getMetadata(const std::string& name) const; + + /// Retrieve the LLSD key we use for one-arg operator() method + std::string getDispatchKey() const { return mKey; } + +private: + template + void addMethod(const std::string& name, const std::string& desc, + const METHOD& method, const LLSD& required) + { + CLASS* downcast = dynamic_cast(this); + if (! downcast) + { + addFail(name, typeid(CLASS).name()); + } + else + { + add(name, desc, boost::bind(method, downcast, _1), required); + } + } + void addFail(const std::string& name, const std::string& classname) const; + + std::string mDesc, mKey; + DispatchMap mDispatch; + + static NameDesc makeNameDesc(const DispatchMap::value_type& item) + { + return NameDesc(item.first, item.second->mDesc); + } + + struct LLSDDispatchEntry; + struct ParamsDispatchEntry; + struct ArrayParamsDispatchEntry; + struct MapParamsDispatchEntry; + + // Step 2 of parameter analysis. Instantiating invoker + // implicitly sets its From and To parameters to the (compile time) begin + // and end iterators over that function's parameter types. + template< typename Function + , class From = typename boost::mpl::begin< boost::function_types::parameter_types >::type + , class To = typename boost::mpl::end< boost::function_types::parameter_types >::type + > + struct invoker; + + // deliver LLSD arguments one at a time + typedef boost::function args_source; + // obtain args from an args_source to build param list and call target + // function + typedef boost::function invoker_function; + + template + invoker_function make_invoker(Function f); + template + invoker_function make_invoker(Method f, const InstanceGetter& getter); + void addArrayParamsDispatchEntry(const std::string& name, + const std::string& desc, + const invoker_function& invoker, + LLSD::Integer arity); + void addMapParamsDispatchEntry(const std::string& name, + const std::string& desc, + const invoker_function& invoker, + const LLSD& params, + const LLSD& defaults); +}; + +/***************************************************************************** +* LLEventDispatcher template implementation details +*****************************************************************************/ +// Step 3 of parameter analysis, the recursive case. +template +struct LLEventDispatcher::invoker +{ + template + struct remove_cv_ref + : boost::remove_cv< typename boost::remove_reference::type > + { }; + + // apply() accepts an arbitrary boost::fusion sequence as args. It + // examines the next parameter type in the parameter-types sequence + // bounded by From and To, obtains the next LLSD object from the passed + // args_source and constructs an LLSDParam of appropriate type to try + // to convert the value. It then recurs with the next parameter-types + // iterator, passing the args sequence thus far. + template + static inline + void apply(Function func, const args_source& argsrc, Args const & args) + { + typedef typename boost::mpl::deref::type arg_type; + typedef typename boost::mpl::next::type next_iter_type; + typedef typename remove_cv_ref::type plain_arg_type; + + invoker::apply + ( func, argsrc, boost::fusion::push_back(args, LLSDParam(argsrc()))); + } + + // Special treatment for instance (first) parameter of a non-static member + // function. Accept the instance-getter callable, calling that to produce + // the first args value. Since we know we're at the top of the recursion + // chain, we need not also require a partial args sequence from our caller. + template + static inline + void method_apply(Function func, const args_source& argsrc, const InstanceGetter& getter) + { + typedef typename boost::mpl::next::type next_iter_type; + + // Instead of grabbing the first item from argsrc and making an + // LLSDParam of it, call getter() and pass that as the instance param. + invoker::apply + ( func, argsrc, boost::fusion::push_back(boost::fusion::nil(), boost::ref(getter()))); + } +}; + +// Step 4 of parameter analysis, the leaf case. When the general +// invoker logic has advanced From until it matches To, +// the compiler will pick this template specialization. +template +struct LLEventDispatcher::invoker +{ + // the argument list is complete, now call the function + template + static inline + void apply(Function func, const args_source&, Args const & args) + { + boost::fusion::invoke(func, args); + } +}; + +template +typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) +{ + // Construct an invoker_function, a callable accepting const args_source&. + // Add to DispatchMap an ArrayParamsDispatchEntry that will handle the + // caller's LLSD::Array. + addArrayParamsDispatchEntry(name, desc, make_invoker(f), + boost::function_types::function_arity::value); +} + +template +typename boost::enable_if< boost::function_types::is_member_function_pointer >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, + const InstanceGetter& getter) +{ + // Subtract 1 from the compile-time arity because the getter takes care of + // the first parameter. We only need (arity - 1) additional arguments. + addArrayParamsDispatchEntry(name, desc, make_invoker(f, getter), + boost::function_types::function_arity::value - 1); +} + +template +typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f, + const LLSD& params, const LLSD& defaults) +{ + // See comments for previous is_nonmember_callable_builtin add(). + addMapParamsDispatchEntry(name, desc, make_invoker(f), params, defaults); +} + +template +typename boost::enable_if< boost::function_types::is_member_function_pointer >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, + const InstanceGetter& getter, + const LLSD& params, const LLSD& defaults) +{ + addMapParamsDispatchEntry(name, desc, make_invoker(f, getter), params, defaults); +} + +template +LLEventDispatcher::invoker_function +LLEventDispatcher::make_invoker(Function f) +{ + // Step 1 of parameter analysis, the top of the recursion. Passing a + // suitable f (see add()'s enable_if condition) to this method causes it + // to infer the function type; specifying that function type to invoker<> + // causes it to fill in the begin/end MPL iterators over the function's + // list of parameter types. + // While normally invoker::apply() could infer its template type from the + // boost::fusion::nil parameter value, here we must be explicit since + // we're boost::bind()ing it rather than calling it directly. + return boost::bind(&invoker::template apply, + f, + _1, + boost::fusion::nil()); +} + +template +LLEventDispatcher::invoker_function +LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) +{ + // Use invoker::method_apply() to treat the instance (first) arg specially. + return boost::bind(&invoker::template method_apply, + f, + _1, + getter); +} + +/***************************************************************************** +* LLDispatchListener +*****************************************************************************/ +/** + * Bundle an LLEventPump and a listener with an LLEventDispatcher. A class + * that contains (or derives from) LLDispatchListener need only specify the + * LLEventPump name and dispatch key, and add() its methods. Incoming events + * will automatically be dispatched. + */ +class LL_COMMON_API LLDispatchListener: public LLEventDispatcher +{ +public: + LLDispatchListener(const std::string& pumpname, const std::string& key); + + std::string getPumpName() const { return mPump.getName(); } + +private: + bool process(const LLSD& event); + + LLEventStream mPump; + LLTempBoundListener mBoundListener; +}; + +#endif /* ! defined(LL_LLEVENTDISPATCHER_H) */ diff --git a/indra/llcommon/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp new file mode 100644 index 000000000..d36a10725 --- /dev/null +++ b/indra/llcommon/lleventfilter.cpp @@ -0,0 +1,166 @@ +/** + * @file lleventfilter.cpp + * @author Nat Goodspeed + * @date 2009-03-05 + * @brief Implementation for lleventfilter. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lleventfilter.h" +// STL headers +// std headers +// external library headers +#include +// other Linden headers +#include "llerror.h" // LL_ERRS +#include "llsdutil.h" // llsd_matches() + +LLEventFilter::LLEventFilter(LLEventPump& source, const std::string& name, bool tweak): + LLEventStream(name, tweak) +{ + source.listen(getName(), boost::bind(&LLEventFilter::post, this, _1)); +} + +LLEventMatching::LLEventMatching(const LLSD& pattern): + LLEventFilter("matching"), + mPattern(pattern) +{ +} + +LLEventMatching::LLEventMatching(LLEventPump& source, const LLSD& pattern): + LLEventFilter(source, "matching"), + mPattern(pattern) +{ +} + +bool LLEventMatching::post(const LLSD& event) +{ + if (! llsd_matches(mPattern, event).empty()) + return false; + + return LLEventStream::post(event); +} + +LLEventTimeoutBase::LLEventTimeoutBase(): + LLEventFilter("timeout") +{ +} + +LLEventTimeoutBase::LLEventTimeoutBase(LLEventPump& source): + LLEventFilter(source, "timeout") +{ +} + +void LLEventTimeoutBase::actionAfter(F32 seconds, const Action& action) +{ + setCountdown(seconds); + mAction = action; + if (! mMainloop.connected()) + { + LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop")); + mMainloop = mainloop.listen(getName(), boost::bind(&LLEventTimeoutBase::tick, this, _1)); + } +} + +class ErrorAfter +{ +public: + ErrorAfter(const std::string& message): mMessage(message) {} + + void operator()() + { + LL_ERRS("LLEventTimeout") << mMessage << LL_ENDL; + } + +private: + std::string mMessage; +}; + +void LLEventTimeoutBase::errorAfter(F32 seconds, const std::string& message) +{ + actionAfter(seconds, ErrorAfter(message)); +} + +class EventAfter +{ +public: + EventAfter(LLEventPump& pump, const LLSD& event): + mPump(pump), + mEvent(event) + {} + + void operator()() + { + mPump.post(mEvent); + } + +private: + LLEventPump& mPump; + LLSD mEvent; +}; + +void LLEventTimeoutBase::eventAfter(F32 seconds, const LLSD& event) +{ + actionAfter(seconds, EventAfter(*this, event)); +} + +bool LLEventTimeoutBase::post(const LLSD& event) +{ + cancel(); + return LLEventStream::post(event); +} + +void LLEventTimeoutBase::cancel() +{ + mMainloop.disconnect(); +} + +bool LLEventTimeoutBase::tick(const LLSD&) +{ + if (countdownElapsed()) + { + cancel(); + mAction(); + } + return false; // show event to other listeners +} + +LLEventTimeout::LLEventTimeout() {} + +LLEventTimeout::LLEventTimeout(LLEventPump& source): + LLEventTimeoutBase(source) +{ +} + +void LLEventTimeout::setCountdown(F32 seconds) +{ + mTimer.setTimerExpirySec(seconds); +} + +bool LLEventTimeout::countdownElapsed() const +{ + return mTimer.hasExpired(); +} diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h new file mode 100644 index 000000000..e822a664f --- /dev/null +++ b/indra/llcommon/lleventfilter.h @@ -0,0 +1,203 @@ +/** + * @file lleventfilter.h + * @author Nat Goodspeed + * @date 2009-03-05 + * @brief Define LLEventFilter: LLEventStream subclass with conditions + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, 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$ + */ + +#if ! defined(LL_LLEVENTFILTER_H) +#define LL_LLEVENTFILTER_H + +#include "llevents.h" +#include "stdtypes.h" +#include "lltimer.h" +#include + +/** + * Generic base class + */ +class LL_COMMON_API LLEventFilter: public LLEventStream +{ +public: + /// construct a standalone LLEventFilter + LLEventFilter(const std::string& name="filter", bool tweak=true): + LLEventStream(name, tweak) + {} + /// construct LLEventFilter and connect it to the specified LLEventPump + LLEventFilter(LLEventPump& source, const std::string& name="filter", bool tweak=true); + + /// Post an event to all listeners + virtual bool post(const LLSD& event) = 0; +}; + +/** + * Pass through only events matching a specified pattern + */ +class LLEventMatching: public LLEventFilter +{ +public: + /// Pass an LLSD map with keys and values the incoming event must match + LLEventMatching(const LLSD& pattern); + /// instantiate and connect + LLEventMatching(LLEventPump& source, const LLSD& pattern); + + /// Only pass through events matching the pattern + virtual bool post(const LLSD& event); + +private: + LLSD mPattern; +}; + +/** + * Wait for an event to be posted. If no such event arrives within a specified + * time, take a specified action. See LLEventTimeout for production + * implementation. + * + * @NOTE This is an abstract base class so that, for testing, we can use an + * alternate "timer" that doesn't actually consume real time. + */ +class LL_COMMON_API LLEventTimeoutBase: public LLEventFilter +{ +public: + /// construct standalone + LLEventTimeoutBase(); + /// construct and connect + LLEventTimeoutBase(LLEventPump& source); + + /// Callable, can be constructed with boost::bind() + typedef boost::function Action; + + /** + * Start countdown timer for the specified number of @a seconds. Forward + * all events. If any event arrives before timer expires, cancel timer. If + * no event arrives before timer expires, take specified @a action. + * + * This is a one-shot timer. Once it has either expired or been canceled, + * it is inert until another call to actionAfter(). + * + * Calling actionAfter() while an existing timer is running cheaply + * replaces that original timer. Thus, a valid use case is to detect + * idleness of some event source by calling actionAfter() on each new + * event. A rapid sequence of events will keep the timer from expiring; + * the first gap in events longer than the specified timer will fire the + * specified Action. + * + * Any post() call cancels the timer. To be satisfied with only a + * particular event, chain on an LLEventMatching that only passes such + * events: + * + * @code + * event ultimate + * source ---> LLEventMatching ---> LLEventTimeout ---> listener + * @endcode + * + * @NOTE + * The implementation relies on frequent events on the LLEventPump named + * "mainloop". + */ + void actionAfter(F32 seconds, const Action& action); + + /** + * Like actionAfter(), but where the desired Action is LL_ERRS + * termination. Pass the timeout time and the desired LL_ERRS @a message. + * + * This method is useful when, for instance, some async API guarantees an + * event, whether success or failure, within a stated time window. + * Instantiate an LLEventTimeout listening to that API and call + * errorAfter() on each async request with a timeout comfortably longer + * than the API's time guarantee (much longer than the anticipated + * "mainloop" granularity). + * + * Then if the async API breaks its promise, the program terminates with + * the specified LL_ERRS @a message. The client of the async API can + * therefore assume the guarantee is upheld. + * + * @NOTE + * errorAfter() is implemented in terms of actionAfter(), so all remarks + * about calling actionAfter() also apply to errorAfter(). + */ + void errorAfter(F32 seconds, const std::string& message); + + /** + * Like actionAfter(), but where the desired Action is a particular event + * for all listeners. Pass the timeout time and the desired @a event data. + * + * Suppose the timeout should only be satisfied by a particular event, but + * the ultimate listener must see all other incoming events as well, plus + * the timeout @a event if any: + * + * @code + * some LLEventMatching LLEventMatching + * event ---> for particular ---> LLEventTimeout ---> for timeout + * source event event \ + * \ \ ultimate + * `-----------------------------------------------------> listener + * @endcode + * + * Since a given listener can listen on more than one LLEventPump, we can + * set things up so it sees the set union of events from LLEventTimeout + * and the original event source. However, as LLEventTimeout passes + * through all incoming events, the "particular event" that satisfies the + * left LLEventMatching would reach the ultimate listener twice. So we add + * an LLEventMatching that only passes timeout events. + * + * @NOTE + * eventAfter() is implemented in terms of actionAfter(), so all remarks + * about calling actionAfter() also apply to eventAfter(). + */ + void eventAfter(F32 seconds, const LLSD& event); + + /// Pass event through, canceling the countdown timer + virtual bool post(const LLSD& event); + + /// Cancel timer without event + void cancel(); + +protected: + virtual void setCountdown(F32 seconds) = 0; + virtual bool countdownElapsed() const = 0; + +private: + bool tick(const LLSD&); + + LLBoundListener mMainloop; + Action mAction; +}; + +/// Production implementation of LLEventTimoutBase +class LL_COMMON_API LLEventTimeout: public LLEventTimeoutBase +{ +public: + LLEventTimeout(); + LLEventTimeout(LLEventPump& source); + +protected: + virtual void setCountdown(F32 seconds); + virtual bool countdownElapsed() const; + +private: + LLTimer mTimer; +}; + +#endif /* ! defined(LL_LLEVENTFILTER_H) */ diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp new file mode 100644 index 000000000..9bc61ccb5 --- /dev/null +++ b/indra/llcommon/llevents.cpp @@ -0,0 +1,628 @@ +/** + * @file llevents.cpp + * @author Nat Goodspeed + * @date 2008-09-12 + * @brief Implementation for llevents. + * + * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" + +#if LL_WINDOWS +#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want! +#endif + +// associated header +#include "llevents.h" +// STL headers +#include +#include +#include +// std headers +#include +#include +#include +#include +// external library headers +#include +#if LL_WINDOWS +#pragma warning (push) +#pragma warning (disable : 4701) // compiler thinks might use uninitialized var, but no +#endif +#include +#if LL_WINDOWS +#pragma warning (pop) +#endif +// other Linden headers +#include "stringize.h" +#include "llerror.h" +#include "llsdutil.h" +#if LL_MSVC +#pragma warning (disable : 4702) +#endif + +/***************************************************************************** +* queue_names: specify LLEventPump names that should be instantiated as +* LLEventQueue +*****************************************************************************/ +/** + * At present, we recognize particular requested LLEventPump names as needing + * LLEventQueues. Later on we'll migrate this information to an external + * configuration file. + */ +const char* queue_names[] = +{ + "placeholder - replace with first real name string" +}; + +/***************************************************************************** +* If there's a "mainloop" pump, listen on that to flush all LLEventQueues +*****************************************************************************/ +struct RegisterFlush : public LLEventTrackable +{ + RegisterFlush(): + pumps(LLEventPumps::instance()) + { + pumps.obtain("mainloop").listen("flushLLEventQueues", boost::bind(&RegisterFlush::flush, this, _1)); + } + bool flush(const LLSD&) + { + pumps.flush(); + return false; + } + ~RegisterFlush() + { + // LLEventTrackable handles stopListening for us. + } + LLEventPumps& pumps; +}; +static RegisterFlush registerFlush; + +/***************************************************************************** +* LLEventPumps +*****************************************************************************/ +LLEventPumps::LLEventPumps(): + // Until we migrate this information to an external config file, + // initialize mQueueNames from the static queue_names array. + mQueueNames(boost::begin(queue_names), boost::end(queue_names)) +{ +} + +LLEventPump& LLEventPumps::obtain(const std::string& name) +{ + PumpMap::iterator found = mPumpMap.find(name); + if (found != mPumpMap.end()) + { + // Here we already have an LLEventPump instance with the requested + // name. + return *found->second; + } + // Here we must instantiate an LLEventPump subclass. + LLEventPump* newInstance; + // Should this name be an LLEventQueue? + PumpNames::const_iterator nfound = mQueueNames.find(name); + if (nfound != mQueueNames.end()) + newInstance = new LLEventQueue(name); + else + newInstance = new LLEventStream(name); + // LLEventPump's constructor implicitly registers each new instance in + // mPumpMap. But remember that we instantiated it (in mOurPumps) so we'll + // delete it later. + mOurPumps.insert(newInstance); + return *newInstance; +} + +void LLEventPumps::flush() +{ + // Flush every known LLEventPump instance. Leave it up to each instance to + // decide what to do with the flush() call. + for (PumpMap::iterator pmi = mPumpMap.begin(), pmend = mPumpMap.end(); pmi != pmend; ++pmi) + { + pmi->second->flush(); + } +} + +void LLEventPumps::reset() +{ + // Reset every known LLEventPump instance. Leave it up to each instance to + // decide what to do with the reset() call. + for (PumpMap::iterator pmi = mPumpMap.begin(), pmend = mPumpMap.end(); pmi != pmend; ++pmi) + { + pmi->second->reset(); + } +} + +std::string LLEventPumps::registerNew(const LLEventPump& pump, const std::string& name, bool tweak) +{ + std::pair inserted = + mPumpMap.insert(PumpMap::value_type(name, const_cast(&pump))); + // If the insert worked, then the name is unique; return that. + if (inserted.second) + return name; + // Here the new entry was NOT inserted, and therefore name isn't unique. + // Unless we're permitted to tweak it, that's Bad. + if (! tweak) + { + throw LLEventPump::DupPumpName(std::string("Duplicate LLEventPump name '") + name + "'"); + } + // The passed name isn't unique, but we're permitted to tweak it. Find the + // first decimal-integer suffix not already taken. The insert() attempt + // above will have set inserted.first to the iterator of the existing + // entry by that name. Starting there, walk forward until we reach an + // entry that doesn't start with 'name'. For each entry consisting of name + // + integer suffix, capture the integer suffix in a set. Use a set + // because we're going to encounter string suffixes in the order: name1, + // name10, name11, name2, ... Walking those possibilities in that order + // isn't convenient to detect the first available "hole." + std::set suffixes; + PumpMap::iterator pmi(inserted.first), pmend(mPumpMap.end()); + // We already know inserted.first references the existing entry with + // 'name' as the key; skip that one and start with the next. + while (++pmi != pmend) + { + if (pmi->first.substr(0, name.length()) != name) + { + // Found the first entry beyond the entries starting with 'name': + // stop looping. + break; + } + // Here we're looking at an entry that starts with 'name'. Is the rest + // of it an integer? + // Dubious (?) assumption: in the local character set, decimal digits + // are in increasing order such that '9' is the last of them. This + // test deals with 'name' values such as 'a', where there might be a + // very large number of entries starting with 'a' whose suffixes + // aren't integers. A secondary assumption is that digit characters + // precede most common name characters (true in ASCII, false in + // EBCDIC). The test below is correct either way, but it's worth more + // if the assumption holds. + if (pmi->first[name.length()] > '9') + break; + // It should be cheaper to detect that we're not looking at a digit + // character -- and therefore the suffix can't possibly be an integer + // -- than to attempt the lexical_cast and catch the exception. + if (! std::isdigit(pmi->first[name.length()])) + continue; + // Okay, the first character of the suffix is a digit, it's worth at + // least attempting to convert to int. + try + { + suffixes.insert(boost::lexical_cast(pmi->first.substr(name.length()))); + } + catch (const boost::bad_lexical_cast&) + { + // If the rest of pmi->first isn't an int, just ignore it. + } + } + // Here we've accumulated in 'suffixes' all existing int suffixes of the + // entries starting with 'name'. Find the first unused one. + int suffix = 1; + for ( ; suffixes.find(suffix) != suffixes.end(); ++suffix) + ; + // Here 'suffix' is not in 'suffixes'. Construct a new name based on that + // suffix, insert it and return it. + std::ostringstream out; + out << name << suffix; + return registerNew(pump, out.str(), tweak); +} + +void LLEventPumps::unregister(const LLEventPump& pump) +{ + // Remove this instance from mPumpMap + PumpMap::iterator found = mPumpMap.find(pump.getName()); + if (found != mPumpMap.end()) + { + mPumpMap.erase(found); + } + // If this instance is one we created, also remove it from mOurPumps so we + // won't try again to delete it later! + PumpSet::iterator psfound = mOurPumps.find(const_cast(&pump)); + if (psfound != mOurPumps.end()) + { + mOurPumps.erase(psfound); + } +} + +//static +bool LLEventPumps::sDeleted; + +//static +void LLEventPumps::maybe_unregister(const LLEventPump& pump) +{ + if (!sDeleted) + { + LLEventPumps::instance().unregister(pump); + } +} + +LLEventPumps::~LLEventPumps() +{ + // Deleting an LLEventPump calls its destructor, which calls maybe_unregister(), + // which would try to remove that LLEventPump instance from a NEWLY created LLEventPumps + // singleton (as we're already being destructed). Therefore, mark that we're not + // home anymore... --Aleric + sDeleted = true; + + // Subsequently we can delete every LLEventPump we instantiated (via obtain()). + // We're not clearing mPumpMap or mOurPumps here... their destructors will. + for (LLEventPumps::PumpSet::iterator pump = mOurPumps.begin(); pump != mOurPumps.end(); ++pump) + { + delete *pump; + } +} + +/***************************************************************************** +* LLEventPump +*****************************************************************************/ +#if LL_WINDOWS +#pragma warning (push) +#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally +#endif + +LLEventPump::LLEventPump(const std::string& name, bool tweak): + // Register every new instance with LLEventPumps + mName(LLEventPumps::instance().registerNew(*this, name, tweak)), + mSignal(new LLStandardSignal()), + mEnabled(true) +{} + +#if LL_WINDOWS +#pragma warning (pop) +#endif + +LLEventPump::~LLEventPump() +{ + // Unregister this doomed instance from LLEventPumps + LLEventPumps::maybe_unregister(*this); +} + +// static data member +const LLEventPump::NameList LLEventPump::empty; + +std::string LLEventPump::inventName(const std::string& pfx) +{ + static long suffix = 0; + return STRINGIZE(pfx << suffix++); +} + +void LLEventPump::reset() +{ + mSignal.reset(); + mConnections.clear(); + //mDeps.clear(); +} + +LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventListener& listener, + const NameList& after, + const NameList& before) +{ + // Check for duplicate name before connecting listener to mSignal + ConnectionMap::const_iterator found = mConnections.find(name); + // In some cases the user might disconnect a connection explicitly -- or + // might use LLEventTrackable to disconnect implicitly. Either way, we can + // end up retaining in mConnections a zombie connection object that's + // already been disconnected. Such a connection object can't be + // reconnected -- nor, in the case of LLEventTrackable, would we want to + // try, since disconnection happens with the destruction of the listener + // object. That means it's safe to overwrite a disconnected connection + // object with the new one we're attempting. The case we want to prevent + // is only when the existing connection object is still connected. + if (found != mConnections.end() && found->second.connected()) + { + throw DupListenerName(std::string("Attempt to register duplicate listener name '") + name + + "' on " + typeid(*this).name() + " '" + getName() + "'"); + } + // Okay, name is unique, try to reconcile its dependencies. Specify a new + // "node" value that we never use for an mSignal placement; we'll fix it + // later. + DependencyMap::node_type& newNode = mDeps.add(name, -1.0, after, before); + // What if this listener has been added, removed and re-added? In that + // case newNode already has a non-negative value because we never remove a + // listener from mDeps. But keep processing uniformly anyway in case the + // listener was added back with different dependencies. Then mDeps.sort() + // would put it in a different position, and the old newNode placement + // value would be wrong, so we'd have to reassign it anyway. Trust that + // re-adding a listener with the same dependencies is the trivial case for + // mDeps.sort(): it can just replay its cache. + DependencyMap::sorted_range sorted_range; + try + { + // Can we pick an order that works including this new entry? + sorted_range = mDeps.sort(); + } + catch (const DependencyMap::Cycle& e) + { + // No: the new node's after/before dependencies have made mDeps + // unsortable. If we leave the new node in mDeps, it will continue + // to screw up all future attempts to sort()! Pull it out. + mDeps.remove(name); + throw Cycle(std::string("New listener '") + name + "' on " + typeid(*this).name() + + " '" + getName() + "' would cause cycle: " + e.what()); + } + // Walk the list to verify that we haven't changed the order. + float previous = 0.0, myprev = 0.0; + DependencyMap::sorted_iterator mydmi = sorted_range.end(); // need this visible after loop + for (DependencyMap::sorted_iterator dmi = sorted_range.begin(); + dmi != sorted_range.end(); ++dmi) + { + // Since we've added the new entry with an invalid placement, + // recognize it and skip it. + if (dmi->first == name) + { + // Remember the iterator belonging to our new node, and which + // placement value was 'previous' at that point. + mydmi = dmi; + myprev = previous; + continue; + } + // If the new node has rearranged the existing nodes, we'll find + // that their placement values are no longer in increasing order. + if (dmi->second < previous) + { + // This is another scenario in which we'd better back out the + // newly-added node from mDeps -- but don't do it yet, we want to + // traverse the existing mDeps to report on it! + // Describe the change to the order of our listeners. Copy + // everything but the newest listener to a vector we can sort to + // obtain the old order. + typedef std::vector< std::pair > SortNameList; + SortNameList sortnames; + for (DependencyMap::sorted_iterator cdmi(sorted_range.begin()), cdmend(sorted_range.end()); + cdmi != cdmend; ++cdmi) + { + if (cdmi->first != name) + { + sortnames.push_back(SortNameList::value_type(cdmi->second, cdmi->first)); + } + } + std::sort(sortnames.begin(), sortnames.end()); + std::ostringstream out; + out << "New listener '" << name << "' on " << typeid(*this).name() << " '" << getName() + << "' would move previous listener '" << dmi->first << "'\nwas: "; + SortNameList::const_iterator sni(sortnames.begin()), snend(sortnames.end()); + if (sni != snend) + { + out << sni->second; + while (++sni != snend) + { + out << ", " << sni->second; + } + } + out << "\nnow: "; + DependencyMap::sorted_iterator ddmi(sorted_range.begin()), ddmend(sorted_range.end()); + if (ddmi != ddmend) + { + out << ddmi->first; + while (++ddmi != ddmend) + { + out << ", " << ddmi->first; + } + } + // NOW remove the offending listener node. + mDeps.remove(name); + // Having constructed a description of the order change, inform caller. + throw OrderChange(out.str()); + } + // This node becomes the previous one. + previous = dmi->second; + } + // We just got done with a successful mDeps.add(name, ...) call. We'd + // better have found 'name' somewhere in that sorted list! + assert(mydmi != sorted_range.end()); + // Four cases: + // 0. name is the only entry: placement 1.0 + // 1. name is the first of several entries: placement (next placement)/2 + // 2. name is between two other entries: placement (myprev + (next placement))/2 + // 3. name is the last entry: placement ceil(myprev) + 1.0 + // Since we've cleverly arranged for myprev to be 0.0 if name is the + // first entry, this folds down to two cases. Case 1 is subsumed by + // case 2, and case 0 is subsumed by case 3. So we need only handle + // cases 2 and 3, which means we need only detect whether name is the + // last entry. Increment mydmi to see if there's anything beyond. + if (++mydmi != sorted_range.end()) + { + // The new node isn't last. Place it between the previous node and + // the successor. + newNode = (myprev + mydmi->second)/2.0; + } + else + { + // The new node is last. Bump myprev up to the next integer, add + // 1.0 and use that. + newNode = std::ceil(myprev) + 1.0; + } + // Now that newNode has a value that places it appropriately in mSignal, + // connect it. + LLBoundListener bound = mSignal->connect(newNode, listener); + mConnections[name] = bound; + return bound; +} + +LLBoundListener LLEventPump::getListener(const std::string& name) const +{ + ConnectionMap::const_iterator found = mConnections.find(name); + if (found != mConnections.end()) + { + return found->second; + } + // not found, return dummy LLBoundListener + return LLBoundListener(); +} + +void LLEventPump::stopListening(const std::string& name) +{ + ConnectionMap::iterator found = mConnections.find(name); + if (found != mConnections.end()) + { + found->second.disconnect(); + mConnections.erase(found); + } + // We intentionally do NOT remove this name from mDeps. It may happen that + // the same listener with the same name and dependencies will jump on and + // off this LLEventPump repeatedly. Keeping a cache of dependencies will + // avoid a new dependency sort in such cases. +} + +/***************************************************************************** +* LLEventStream +*****************************************************************************/ +bool LLEventStream::post(const LLSD& event) +{ + if (! mEnabled || !mSignal) + { + return false; + } + // NOTE NOTE NOTE: Any new access to member data beyond this point should + // cause us to move our LLStandardSignal object to a pimpl class along + // with said member data. Then the local shared_ptr will preserve both. + + // DEV-43463: capture a local copy of mSignal. We've turned up a + // cross-coroutine scenario (described in the Jira) in which this post() + // call could end up destroying 'this', the LLEventPump subclass instance + // containing mSignal, during the call through *mSignal. So -- capture a + // *stack* instance of the shared_ptr, ensuring that our heap + // LLStandardSignal object will live at least until post() returns, even + // if 'this' gets destroyed during the call. + boost::shared_ptr signal(mSignal); + // Let caller know if any one listener handled the event. This is mostly + // useful when using LLEventStream as a listener for an upstream + // LLEventPump. + return (*signal)(event); +} + +/***************************************************************************** +* LLEventQueue +*****************************************************************************/ +bool LLEventQueue::post(const LLSD& event) +{ + if (mEnabled) + { + // Defer sending this event by queueing it until flush() + mEventQueue.push_back(event); + } + // Unconditionally return false. We won't know until flush() whether a + // listener claims to have handled the event -- meanwhile, don't block + // other listeners. + return false; +} + +void LLEventQueue::flush() +{ + if(!mSignal) return; + + // Consider the case when a given listener on this LLEventQueue posts yet + // another event on the same queue. If we loop over mEventQueue directly, + // we'll end up processing all those events during the same flush() call + // -- rather like an EventStream. Instead, copy mEventQueue and clear it, + // so that any new events posted to this LLEventQueue during flush() will + // be processed in the *next* flush() call. + EventQueue queue(mEventQueue); + mEventQueue.clear(); + // NOTE NOTE NOTE: Any new access to member data beyond this point should + // cause us to move our LLStandardSignal object to a pimpl class along + // with said member data. Then the local shared_ptr will preserve both. + + // DEV-43463: capture a local copy of mSignal. See LLEventStream::post() + // for detailed comments. + boost::shared_ptr signal(mSignal); + for ( ; ! queue.empty(); queue.pop_front()) + { + (*signal)(queue.front()); + } +} + +/***************************************************************************** +* LLListenerOrPumpName +*****************************************************************************/ +LLListenerOrPumpName::LLListenerOrPumpName(const std::string& pumpname): + // Look up the specified pumpname, and bind its post() method as our listener + mListener(boost::bind(&LLEventPump::post, + boost::ref(LLEventPumps::instance().obtain(pumpname)), + _1)) +{ +} + +LLListenerOrPumpName::LLListenerOrPumpName(const char* pumpname): + // Look up the specified pumpname, and bind its post() method as our listener + mListener(boost::bind(&LLEventPump::post, + boost::ref(LLEventPumps::instance().obtain(pumpname)), + _1)) +{ +} + +bool LLListenerOrPumpName::operator()(const LLSD& event) const +{ + if (! mListener) + { + throw Empty("attempting to call uninitialized"); + } + return (*mListener)(event); +} + +void LLReqID::stamp(LLSD& response) const +{ + if (! (response.isUndefined() || response.isMap())) + { + // If 'response' was previously completely empty, it's okay to + // turn it into a map. If it was already a map, then it should be + // okay to add a key. But if it was anything else (e.g. a scalar), + // assigning a ["reqid"] key will DISCARD the previous value, + // replacing it with a map. That would be Bad. + LL_INFOS("LLReqID") << "stamp(" << mReqid << ") leaving non-map response unmodified: " + << response << LL_ENDL; + return; + } + LLSD oldReqid(response["reqid"]); + if (! (oldReqid.isUndefined() || llsd_equals(oldReqid, mReqid))) + { + LL_INFOS("LLReqID") << "stamp(" << mReqid << ") preserving existing [\"reqid\"] value " + << oldReqid << " in response: " << response << LL_ENDL; + return; + } + response["reqid"] = mReqid; +} + +bool sendReply(const LLSD& reply, const LLSD& request, const std::string& replyKey) +{ + // If the original request has no value for replyKey, it's pointless to + // construct or send a reply event: on which LLEventPump should we send + // it? Allow that to be optional: if the caller wants to require replyKey, + // it can so specify when registering the operation method. + if (! request.has(replyKey)) + { + return false; + } + + // Here the request definitely contains replyKey; reasonable to proceed. + + // Copy 'reply' to modify it. + LLSD newreply(reply); + // Get the ["reqid"] element from request + LLReqID reqID(request); + // and copy it to 'newreply'. + reqID.stamp(newreply); + // Send reply on LLEventPump named in request[replyKey]. Don't forget to + // send the modified 'newreply' instead of the original 'reply'. + return LLEventPumps::instance().obtain(request[replyKey]).post(newreply); +} diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h new file mode 100644 index 000000000..4f8fb1d3f --- /dev/null +++ b/indra/llcommon/llevents.h @@ -0,0 +1,1051 @@ +/** + * @file llevents.h + * @author Kent Quirk, Nat Goodspeed + * @date 2008-09-11 + * @brief This is an implementation of the event system described at + * https://wiki.lindenlab.com/wiki/Viewer:Messaging/Event_System, + * originally introduced in llnotifications.h. It has nothing + * whatsoever to do with the older system in llevent.h. + * + * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, 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$ + */ + +#if ! defined(LL_LLEVENTS_H) +#define LL_LLEVENTS_H + +#include +#include +#include +#include +#include +#include +#if LL_WINDOWS + #pragma warning (push) + #pragma warning (disable : 4263) // boost::signals2::expired_slot::what() has const mismatch + #pragma warning (disable : 4264) +#endif +#include +#if LL_WINDOWS + #pragma warning (pop) +#endif + +#include +#include +#include +#include // noncopyable +#include +#include +#include // reference_wrapper +#include +#include +#include +#include "llsd.h" +#include "llsingleton.h" +#include "lldependencies.h" +#include "ll_template_cast.h" + +/*==========================================================================*| +// override this to allow binding free functions with more parameters +#ifndef LLEVENTS_LISTENER_ARITY +#define LLEVENTS_LISTENER_ARITY 10 +#endif +|*==========================================================================*/ + +// hack for testing +#ifndef testable +#define testable private +#endif + +/***************************************************************************** +* Signal and handler declarations +* Using a single handler signature means that we can have a common handler +* type, rather than needing a distinct one for each different handler. +*****************************************************************************/ + +/** + * A boost::signals2 Combiner that stops the first time a handler returns true + * We need this because we want to have our handlers return bool, so that + * we have the option to cause a handler to stop further processing. The + * default handler fails when the signal returns a value but has no slots. + */ +struct LLStopWhenHandled +{ + typedef bool result_type; + + template + result_type operator()(InputIterator first, InputIterator last) const + { + for (InputIterator si = first; si != last; ++si) + { + if (*si) + { + return true; + } + } + return false; + } +}; + +/** + * We want to have a standard signature for all signals; this way, + * we can easily document a protocol for communicating across + * dlls and into scripting languages someday. + * + * We want to return a bool to indicate whether the signal has been + * handled and should NOT be passed on to other listeners. + * Return true to stop further handling of the signal, and false + * to continue. + * + * We take an LLSD because this way the contents of the signal + * are independent of the API used to communicate it. + * It is const ref because then there's low cost to pass it; + * if you only need to inspect it, it's very cheap. + * + * @internal + * The @c float template parameter indicates that we will internally use @c + * float to indicate relative listener order on a given LLStandardSignal. + * Don't worry, the @c float values are strictly internal! They are not part + * of the interface, for the excellent reason that requiring the caller to + * specify a numeric key to establish order means that the caller must know + * the universe of possible values. We use LLDependencies for that instead. + */ +typedef boost::signals2::signal LLStandardSignal; +/// Methods that forward listeners (e.g. constructed with +/// boost::bind()) should accept (const LLEventListener&) +typedef LLStandardSignal::slot_type LLEventListener; +/// Result of registering a listener, supports connected(), +/// disconnect() and blocked() +typedef boost::signals2::connection LLBoundListener; +/// Storing an LLBoundListener in LLTempBoundListener will disconnect the +/// referenced listener when the LLTempBoundListener instance is destroyed. +typedef boost::signals2::scoped_connection LLTempBoundListener; + +/** + * A common idiom for event-based code is to accept either a callable -- + * directly called on completion -- or the string name of an LLEventPump on + * which to post the completion event. Specifying a parameter as const + * LLListenerOrPumpName& allows either. + * + * Calling a validly-constructed LLListenerOrPumpName, passing the LLSD + * 'event' object, either calls the callable or posts the event to the named + * LLEventPump. + * + * A default-constructed LLListenerOrPumpName is 'empty'. (This is useful as + * the default value of an optional method parameter.) Calling it throws + * LLListenerOrPumpName::Empty. Test for this condition beforehand using + * either if (param) or if (! param). + */ +class LL_COMMON_API LLListenerOrPumpName +{ +public: + /// passing string name of LLEventPump + LLListenerOrPumpName(const std::string& pumpname); + /// passing string literal (overload so compiler isn't forced to infer + /// double conversion) + LLListenerOrPumpName(const char* pumpname); + /// passing listener -- the "anything else" catch-all case. The type of an + /// object constructed by boost::bind() isn't intended to be written out. + /// Normally we'd just accept 'const LLEventListener&', but that would + /// require double implicit conversion: boost::bind() object to + /// LLEventListener, LLEventListener to LLListenerOrPumpName. So use a + /// template to forward anything. + template + LLListenerOrPumpName(const T& listener): mListener(listener) {} + + /// for omitted method parameter: uninitialized mListener + LLListenerOrPumpName() {} + + /// test for validity + operator bool() const { return bool(mListener); } + bool operator! () const { return ! mListener; } + + /// explicit accessor + const LLEventListener& getListener() const { return *mListener; } + + /// implicit conversion to LLEventListener + operator LLEventListener() const { return *mListener; } + + /// allow calling directly + bool operator()(const LLSD& event) const; + + /// exception if you try to call when empty + struct Empty: public std::runtime_error + { + Empty(const std::string& what): + std::runtime_error(std::string("LLListenerOrPumpName::Empty: ") + what) {} + }; + +private: + boost::optional mListener; +}; + +/***************************************************************************** +* LLEventPumps +*****************************************************************************/ +class LLEventPump; + +/** + * LLEventPumps is a Singleton manager through which one typically accesses + * this subsystem. + */ +class LL_COMMON_API LLEventPumps: public LLSingleton +{ + friend class LLSingleton; +public: + /** + * Find or create an LLEventPump instance with a specific name. We return + * a reference so there's no question about ownership. obtain() @em finds + * an instance without conferring @em ownership. + */ + LLEventPump& obtain(const std::string& name); + /** + * Flush all known LLEventPump instances + */ + void flush(); + + /** + * Reset all known LLEventPump instances + * workaround for DEV-35406 crash on shutdown + */ + void reset(); + +private: + friend class LLEventPump; + /** + * Register a new LLEventPump instance (internal) + */ + std::string registerNew(const LLEventPump&, const std::string& name, bool tweak); + /** + * Unregister a doomed LLEventPump instance (internal) + */ + void unregister(const LLEventPump&); + static bool sDeleted; + static void maybe_unregister(const LLEventPump&); + +private: + LLEventPumps(); + ~LLEventPumps(); + +testable: + // Map of all known LLEventPump instances, whether or not we instantiated + // them. We store a plain old LLEventPump* because this map doesn't claim + // ownership of the instances. Though the common usage pattern is to + // request an instance using obtain(), it's fair to instantiate an + // LLEventPump subclass statically, as a class member, on the stack or on + // the heap. In such cases, the instantiating party is responsible for its + // lifespan. + typedef std::map PumpMap; + PumpMap mPumpMap; + // Set of all LLEventPumps we instantiated. Membership in this set means + // we claim ownership, and will delete them when this LLEventPumps is + // destroyed. + typedef std::set PumpSet; + PumpSet mOurPumps; + // LLEventPump names that should be instantiated as LLEventQueue rather + // than as LLEventStream + typedef std::set PumpNames; + PumpNames mQueueNames; +}; + +/***************************************************************************** +* details +*****************************************************************************/ +namespace LLEventDetail +{ + /// Any callable capable of connecting an LLEventListener to an + /// LLStandardSignal to produce an LLBoundListener can be mapped to this + /// signature. + typedef boost::function ConnectFunc; + + /// overload of visit_and_connect() when we have a string identifier available + template + LLBoundListener visit_and_connect(const std::string& name, + const LISTENER& listener, + const ConnectFunc& connect_func); + /** + * Utility template function to use Visitor appropriately + * + * @param listener Callable to connect, typically a boost::bind() + * expression. This will be visited by Visitor using boost::visit_each(). + * @param connect_func Callable that will connect() @a listener to an + * LLStandardSignal, returning LLBoundListener. + */ + template + LLBoundListener visit_and_connect(const LISTENER& listener, + const ConnectFunc& connect_func) + { + return visit_and_connect("", listener, connect_func); + } +} // namespace LLEventDetail + +/***************************************************************************** +* LLEventTrackable +*****************************************************************************/ +/** + * LLEventTrackable wraps boost::signals2::trackable, which resembles + * boost::trackable. Derive your listener class from LLEventTrackable instead, + * and use something like + * LLEventPump::listen(boost::bind(&YourTrackableSubclass::method, + * instance, _1)). This will implicitly disconnect when the object + * referenced by @c instance is destroyed. + * + * @note + * LLEventTrackable doesn't address a couple of cases: + * * Object destroyed during call + * - You enter a slot call in thread A. + * - Thread B destroys the object, which of course disconnects it from any + * future slot calls. + * - Thread A's call uses 'this', which now refers to a defunct object. + * Undefined behavior results. + * * Call during destruction + * - @c MySubclass is derived from LLEventTrackable. + * - @c MySubclass registers one of its own methods using + * LLEventPump::listen(). + * - The @c MySubclass object begins destruction. ~MySubclass() + * runs, destroying state specific to the subclass. (For instance, a + * Foo* data member is deleted but not zeroed.) + * - The listening method will not be disconnected until + * ~LLEventTrackable() runs. + * - Before we get there, another thread posts data to the @c LLEventPump + * instance, calling the @c MySubclass method. + * - The method in question relies on valid @c MySubclass state. (For + * instance, it attempts to dereference the Foo* pointer that was + * deleted but not zeroed.) + * - Undefined behavior results. + * If you suspect you may encounter any such scenario, you're better off + * managing the lifespan of your object with boost::shared_ptr. + * Passing LLEventPump::listen() a boost::bind() expression + * involving a boost::weak_ptr is recognized specially, engaging + * thread-safe Boost.Signals2 machinery. + */ +typedef boost::signals2::trackable LLEventTrackable; + +/***************************************************************************** +* LLEventPump +*****************************************************************************/ +/** + * LLEventPump is the base class interface through which we access the + * concrete subclasses LLEventStream and LLEventQueue. + * + * @NOTE + * LLEventPump derives from LLEventTrackable so that when you "chain" + * LLEventPump instances together, they will automatically disconnect on + * destruction. Please see LLEventTrackable documentation for situations in + * which this may be perilous across threads. + */ +class LL_COMMON_API LLEventPump: public LLEventTrackable +{ +public: + /** + * Exception thrown by LLEventPump(). You are trying to instantiate an + * LLEventPump (subclass) using the same name as some other instance, and + * you didn't pass tweak=true to permit it to generate a unique + * variant. + */ + struct DupPumpName: public std::runtime_error + { + DupPumpName(const std::string& what): + std::runtime_error(std::string("DupPumpName: ") + what) {} + }; + + /** + * Instantiate an LLEventPump (subclass) with the string name by which it + * can be found using LLEventPumps::obtain(). + * + * If you pass (or default) @a tweak to @c false, then a duplicate name + * will throw DupPumpName. This won't happen if LLEventPumps::obtain() + * instantiates the LLEventPump, because obtain() uses find-or-create + * logic. It can only happen if you instantiate an LLEventPump in your own + * code -- and a collision with the name of some other LLEventPump is + * likely to cause much more subtle problems! + * + * When you hand-instantiate an LLEventPump, consider passing @a tweak as + * @c true. This directs LLEventPump() to append a suffix to the passed @a + * name to make it unique. You can retrieve the adjusted name by calling + * getName() on your new instance. + */ + LLEventPump(const std::string& name, bool tweak=false); + virtual ~LLEventPump(); + + /// group exceptions thrown by listen(). We use exceptions because these + /// particular errors are likely to be coding errors, found and fixed by + /// the developer even before preliminary checkin. + struct ListenError: public std::runtime_error + { + ListenError(const std::string& what): std::runtime_error(what) {} + }; + /** + * exception thrown by listen(). You are attempting to register a + * listener on this LLEventPump using the same listener name as an + * already-registered listener. + */ + struct DupListenerName: public ListenError + { + DupListenerName(const std::string& what): + ListenError(std::string("DupListenerName: ") + what) + {} + }; + /** + * exception thrown by listen(). The order dependencies specified for your + * listener are incompatible with existing listeners. + * + * Consider listener "a" which specifies before "b" and "b" which + * specifies before "c". You are now attempting to register "c" before + * "a". There is no order that can satisfy all constraints. + */ + struct Cycle: public ListenError + { + Cycle(const std::string& what): ListenError(std::string("Cycle: ") + what) {} + }; + /** + * exception thrown by listen(). This one means that your new listener + * would force a change to the order of previously-registered listeners, + * and we don't have a good way to implement that. + * + * Consider listeners "some", "other" and "third". "some" and "other" are + * registered earlier without specifying relative order, so "other" + * happens to be first. Now you attempt to register "third" after "some" + * and before "other". Whoops, that would require swapping "some" and + * "other", which we can't do. Instead we throw this exception. + * + * It may not be possible to change the registration order so we already + * know "third"s order requirement by the time we register the second of + * "some" and "other". A solution would be to specify that "some" must + * come before "other", or equivalently that "other" must come after + * "some". + */ + struct OrderChange: public ListenError + { + OrderChange(const std::string& what): ListenError(std::string("OrderChange: ") + what) {} + }; + + /// used by listen() + typedef std::vector NameList; + /// convenience placeholder for when you explicitly want to pass an empty + /// NameList + const static NameList empty; + + /// Get this LLEventPump's name + std::string getName() const { return mName; } + + /** + * Register a new listener with a unique name. Specify an optional list + * of other listener names after which this one must be called, likewise + * an optional list of other listener names before which this one must be + * called. The other listeners mentioned need not yet be registered + * themselves. listen() can throw any ListenError; see ListenError + * subclasses. + * + * The listener name must be unique among active listeners for this + * LLEventPump, else you get DupListenerName. If you don't care to invent + * a name yourself, use inventName(). (I was tempted to recognize e.g. "" + * and internally generate a distinct name for that case. But that would + * handle badly the scenario in which you want to add, remove, re-add, + * etc. the same listener: each new listen() call would necessarily + * perform a new dependency sort. Assuming you specify the same + * after/before lists each time, using inventName() when you first + * instantiate your listener, then passing the same name on each listen() + * call, allows us to optimize away the second and subsequent dependency + * sorts. + * + * If (as is typical) you pass a boost::bind() expression as @a + * listener, listen() will inspect the components of that expression. If a + * bound object matches any of several cases, the connection will + * automatically be disconnected when that object is destroyed. + * + * * You bind a boost::weak_ptr. + * * Binding a boost::shared_ptr that way would ensure that the + * referenced object would @em never be destroyed, since the @c + * shared_ptr stored in the LLEventPump would remain an outstanding + * reference. Use the weaken() function to convert your @c shared_ptr to + * @c weak_ptr. Because this is easy to forget, binding a @c shared_ptr + * will produce a compile error (@c BOOST_STATIC_ASSERT failure). + * * You bind a simple pointer or reference to an object derived from + * boost::enable_shared_from_this. (UNDER CONSTRUCTION) + * * You bind a simple pointer or reference to an object derived from + * LLEventTrackable. Unlike the cases described above, though, this is + * vulnerable to a couple of cross-thread race conditions, as described + * in the LLEventTrackable documentation. + */ + template + LLBoundListener listen(const std::string& name, const LISTENER& listener, + const NameList& after=NameList(), + const NameList& before=NameList()) + { + // Examine listener, using our listen_impl() method to make the + // actual connection. + // This is why listen() is a template. Conversion from boost::bind() + // to LLEventListener performs type erasure, so it's important to look + // at the boost::bind object itself before that happens. + return LLEventDetail::visit_and_connect(name, + listener, + boost::bind(&LLEventPump::listen_impl, + this, + name, + _1, + after, + before)); + } + + /// Get the LLBoundListener associated with the passed name (dummy + /// LLBoundListener if not found) + virtual LLBoundListener getListener(const std::string& name) const; + /** + * Instantiate one of these to block an existing connection: + * @code + * { // in some local scope + * LLEventPump::Blocker block(someLLBoundListener); + * // code that needs the connection blocked + * } // unblock the connection again + * @endcode + */ + typedef boost::signals2::shared_connection_block Blocker; + /// Unregister a listener by name. Prefer this to + /// getListener(name).disconnect() because stopListening() also + /// forgets this name. + virtual void stopListening(const std::string& name); + /// Post an event to all listeners. The @c bool return is only meaningful + /// if the underlying leaf class is LLEventStream -- beware of relying on + /// it too much! Truthfully, we return @c bool mostly to permit chaining + /// one LLEventPump as a listener on another. + virtual bool post(const LLSD&) = 0; + /// Enable/disable: while disabled, silently ignore all post() calls + virtual void enable(bool enabled=true) { mEnabled = enabled; } + /// query + virtual bool enabled() const { return mEnabled; } + + /// Generate a distinct name for a listener -- see listen() + static std::string inventName(const std::string& pfx="listener"); + +private: + friend class LLEventPumps; + /// flush queued events + virtual void flush() {} + + virtual void reset(); + +private: + virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&, + const NameList& after, + const NameList& before); + std::string mName; + +protected: + /// implement the dispatching + boost::shared_ptr mSignal; + + /// valve open? + bool mEnabled; + /// Map of named listeners. This tracks the listeners that actually exist + /// at this moment. When we stopListening(), we discard the entry from + /// this map. + typedef std::map ConnectionMap; + ConnectionMap mConnections; + typedef LLDependencies DependencyMap; + /// Dependencies between listeners. For each listener, track the float + /// used to establish its place in mSignal's order. This caches all the + /// listeners that have ever registered; stopListening() does not discard + /// the entry from this map. This is to avoid a new dependency sort if the + /// same listener with the same dependencies keeps hopping on and off this + /// LLEventPump. + DependencyMap mDeps; +}; + +/***************************************************************************** +* LLEventStream +*****************************************************************************/ +/** + * LLEventStream is a thin wrapper around LLStandardSignal. Posting an + * event immediately calls all registered listeners. + */ +class LL_COMMON_API LLEventStream: public LLEventPump +{ +public: + LLEventStream(const std::string& name, bool tweak=false): LLEventPump(name, tweak) {} + virtual ~LLEventStream() {} + + /// Post an event to all listeners + virtual bool post(const LLSD& event); +}; + +/***************************************************************************** +* LLEventQueue +*****************************************************************************/ +/** + * LLEventQueue isa LLEventPump whose post() method defers calling registered + * listeners until flush() is called. + */ +class LL_COMMON_API LLEventQueue: public LLEventPump +{ +public: + LLEventQueue(const std::string& name, bool tweak=false): LLEventPump(name, tweak) {} + virtual ~LLEventQueue() {} + + /// Post an event to all listeners + virtual bool post(const LLSD& event); + +private: + /// flush queued events + virtual void flush(); + +private: + typedef std::deque EventQueue; + EventQueue mEventQueue; +}; + +/***************************************************************************** +* LLReqID +*****************************************************************************/ +/** + * This class helps the implementer of a given event API to honor the + * ["reqid"] convention. By this convention, each event API stamps into its + * response LLSD a ["reqid"] key whose value echoes the ["reqid"] value, if + * any, from the corresponding request. + * + * This supports an (atypical, but occasionally necessary) use case in which + * two or more asynchronous requests are multiplexed onto the same ["reply"] + * LLEventPump. Since the response events could arrive in arbitrary order, the + * caller must be able to demux them. It does so by matching the ["reqid"] + * value in each response with the ["reqid"] value in the corresponding + * request. + * + * It is the caller's responsibility to ensure distinct ["reqid"] values for + * that case. Though LLSD::UUID is guaranteed to work, it might be overkill: + * the "namespace" of unique ["reqid"] values is simply the set of requests + * specifying the same ["reply"] LLEventPump name. + * + * Making a given event API echo the request's ["reqid"] into the response is + * nearly trivial. This helper is mostly for mnemonic purposes, to serve as a + * place to put these comments. We hope that each time a coder implements a + * new event API based on some existing one, s/he will say, "Huh, what's an + * LLReqID?" and look up this material. + * + * The hardest part about the convention is deciding where to store the + * ["reqid"] value. Ironically, LLReqID can't help with that: you must store + * an LLReqID instance in whatever storage will persist until the reply is + * sent. For example, if the request ultimately ends up using a Responder + * subclass, storing an LLReqID instance in the Responder works. + * + * @note + * The @em implementer of an event API must honor the ["reqid"] convention. + * However, the @em caller of an event API need only use it if s/he is sharing + * the same ["reply"] LLEventPump for two or more asynchronous event API + * requests. + * + * In most cases, it's far easier for the caller to instantiate a local + * LLEventStream and pass its name to the event API in question. Then it's + * perfectly reasonable not to set a ["reqid"] key in the request, ignoring + * the @c isUndefined() ["reqid"] value in the response. + */ +class LL_COMMON_API LLReqID +{ +public: + /** + * If you have the request in hand at the time you instantiate the + * LLReqID, pass that request to extract its ["reqid"]. + */ + LLReqID(const LLSD& request): + mReqid(request["reqid"]) + {} + /// If you don't yet have the request, use setFrom() later. + LLReqID() {} + + /// Extract and store the ["reqid"] value from an incoming request. + void setFrom(const LLSD& request) + { + mReqid = request["reqid"]; + } + + /// Set ["reqid"] key into a pending response LLSD object. + void stamp(LLSD& response) const; + + /// Make a whole new response LLSD object with our ["reqid"]. + LLSD makeResponse() const + { + LLSD response; + stamp(response); + return response; + } + + /// Not really sure of a use case for this accessor... + LLSD getReqID() const { return mReqid; } + +private: + LLSD mReqid; +}; + +/** + * Conventionally send a reply to a request event. + * + * @a reply is the LLSD reply event to send + * @a request is the corresponding LLSD request event + * @a replyKey is the key in the @a request event, conventionally ["reply"], + * whose value is the name of the LLEventPump on which to send the reply. + * + * Before sending the reply event, sendReply() copies the ["reqid"] item from + * the request to the reply. + */ +LL_COMMON_API bool sendReply(const LLSD& reply, const LLSD& request, + const std::string& replyKey="reply"); + +/** + * Base class for LLListenerWrapper. See visit_and_connect() and llwrap(). We + * provide virtual @c accept_xxx() methods, customization points allowing a + * subclass access to certain data visible at LLEventPump::listen() time. + * Example subclass usage: + * + * @code + * myEventPump.listen("somename", + * llwrap(boost::bind(&MyClass::method, instance, _1))); + * @endcode + * + * Because of the anticipated usage (note the anonymous temporary + * MyListenerWrapper instance in the example above), the @c accept_xxx() + * methods must be @c const. + */ +class LL_COMMON_API LLListenerWrapperBase +{ +public: + /// New instance. The accept_xxx() machinery makes it important to use + /// shared_ptrs for our data. Many copies of this object are made before + /// the instance that actually ends up in the signal, yet accept_xxx() + /// will later be called on the @em original instance. All copies of the + /// same original instance must share the same data. + LLListenerWrapperBase(): + mName(new std::string), + mConnection(new LLBoundListener) + { + } + + /// Copy constructor. Copy shared_ptrs to original instance data. + LLListenerWrapperBase(const LLListenerWrapperBase& that): + mName(that.mName), + mConnection(that.mConnection) + { + } + virtual ~LLListenerWrapperBase() {} + + /// Ask LLEventPump::listen() for the listener name + virtual void accept_name(const std::string& name) const + { + *mName = name; + } + + /// Ask LLEventPump::listen() for the new connection + virtual void accept_connection(const LLBoundListener& connection) const + { + *mConnection = connection; + } + +protected: + /// Listener name. + boost::shared_ptr mName; + /// Connection. + boost::shared_ptr mConnection; +}; + +/***************************************************************************** +* Underpinnings +*****************************************************************************/ +/** + * We originally provided a suite of overloaded + * LLEventTrackable::listenTo(LLEventPump&, ...) methods that would call + * LLEventPump::listen(...) and then pass the returned LLBoundListener to + * LLEventTrackable::track(). This was workable but error-prone: the coder + * must remember to call listenTo() rather than the more straightforward + * listen() method. + * + * Now we publish only the single canonical listen() method, so there's a + * uniform mechanism. Having a single way to do this is good, in that there's + * no question in the coder's mind which of several alternatives to choose. + * + * To support automatic connection management, we use boost::visit_each + * (http://www.boost.org/doc/libs/1_37_0/doc/html/boost/visit_each.html) to + * inspect each argument of a boost::bind expression. (Although the visit_each + * mechanism was first introduced with the original Boost.Signals library, it + * was only later documented.) + * + * Cases: + * * At least one of the function's arguments is a boost::weak_ptr. Pass + * the corresponding shared_ptr to slot_type::track(). Ideally that would be + * the object whose method we want to call, but in fact we do the same for + * any weak_ptr we might find among the bound arguments. If we're passing + * our bound method a weak_ptr to some object, wouldn't the destruction of + * that object invalidate the call? So we disconnect automatically when any + * such object is destroyed. This is the mechanism preferred by boost:: + * signals2. + * * One of the functions's arguments is a boost::shared_ptr. This produces + * a compile error: the bound copy of the shared_ptr stored in the + * boost_bind object stored in the signal object would make the referenced + * T object immortal. We provide a weaken() function. Pass + * weaken(your_shared_ptr) instead. (We can inspect, but not modify, the + * boost::bind object. Otherwise we'd replace the shared_ptr with weak_ptr + * implicitly and just proceed.) + * * One of the function's arguments is a plain pointer/reference to an object + * derived from boost::enable_shared_from_this. We assume that this object + * is managed using boost::shared_ptr, so we implicitly extract a shared_ptr + * and track that. (UNDER CONSTRUCTION) + * * One of the function's arguments is derived from LLEventTrackable. Pass + * the LLBoundListener to its LLEventTrackable::track(). This is vulnerable + * to a couple different race conditions, as described in LLEventTrackable + * documentation. (NOTE: Now that LLEventTrackable is a typedef for + * boost::signals2::trackable, the Signals2 library handles this itself, so + * our visitor needs no special logic for this case.) + * * Any other argument type is irrelevant to automatic connection management. + */ + +namespace LLEventDetail +{ + template + const F& unwrap(const F& f) { return f; } + + template + const F& unwrap(const boost::reference_wrapper& f) { return f.get(); } + + // Most of the following is lifted from the Boost.Signals use of + // visit_each. + template struct truth {}; + + /** + * boost::visit_each() Visitor, used on a template argument const F& + * f as follows (see visit_and_connect()): + * @code + * LLEventListener listener(f); + * Visitor visitor(listener); // bind listener so it can track() shared_ptrs + * using boost::visit_each; // allow unqualified visit_each() call for ADL + * visit_each(visitor, unwrap(f)); + * @endcode + */ + class Visitor + { + public: + /** + * Visitor binds a reference to LLEventListener so we can track() any + * shared_ptrs we find in the argument list. + */ + Visitor(LLEventListener& listener): + mListener(listener) + { + } + + /** + * boost::visit_each() calls this method for each component of a + * boost::bind() expression. + */ + template + void operator()(const T& t) const + { + decode(t, 0); + } + + private: + // decode() decides between a reference wrapper and anything else + // boost::ref() variant + template + void decode(const boost::reference_wrapper& t, int) const + { +// add_if_trackable(t.get_pointer()); + } + + // decode() anything else + template + void decode(const T& t, long) const + { + typedef truth<(boost::is_pointer::value)> is_a_pointer; + maybe_get_pointer(t, is_a_pointer()); + } + + // maybe_get_pointer() decides between a pointer and a non-pointer + // plain pointer variant + template + void maybe_get_pointer(const T& t, truth) const + { +// add_if_trackable(t); + } + + // shared_ptr variant + template + void maybe_get_pointer(const boost::shared_ptr& t, truth) const + { + // If we have a shared_ptr to this object, it doesn't matter + // whether the object is derived from LLEventTrackable, so no + // further analysis of T is needed. +// mListener.track(t); + + // Make this case illegal. Passing a bound shared_ptr to + // slot_type::track() is useless, since the bound shared_ptr will + // keep the object alive anyway! Force the coder to cast to weak_ptr. + + // Trivial as it is, make the BOOST_STATIC_ASSERT() condition + // dependent on template param so the macro is only evaluated if + // this method is in fact instantiated, as described here: + // http://www.boost.org/doc/libs/1_34_1/doc/html/boost_staticassert.html + + // ATTENTION: Don't bind a shared_ptr using + // LLEventPump::listen(boost::bind()). Doing so captures a copy of + // the shared_ptr, making the referenced object effectively + // immortal. Use the weaken() function, e.g.: + // somepump.listen(boost::bind(...weaken(my_shared_ptr)...)); + // This lets us automatically disconnect when the referenced + // object is destroyed. + BOOST_STATIC_ASSERT(sizeof(T) == 0); + } + + // weak_ptr variant + template + void maybe_get_pointer(const boost::weak_ptr& t, truth) const + { + // If we have a weak_ptr to this object, it doesn't matter + // whether the object is derived from LLEventTrackable, so no + // further analysis of T is needed. + mListener.track(t); +// std::cout << "Found weak_ptr<" << typeid(T).name() << ">!\n"; + } + +#if 0 + // reference to anything derived from boost::enable_shared_from_this + template + inline void maybe_get_pointer(const boost::enable_shared_from_this& ct, + truth) const + { + // Use the slot_type::track(shared_ptr) mechanism. Cast away + // const-ness because (in our code base anyway) it's unusual + // to find shared_ptr. + boost::enable_shared_from_this& + t(const_cast&>(ct)); + std::cout << "Capturing shared_from_this()" << std::endl; + boost::shared_ptr sp(t.shared_from_this()); +/*==========================================================================*| + std::cout << "Capturing weak_ptr" << std::endl; + boost::weak_ptr wp(sp); +|*==========================================================================*/ + std::cout << "Tracking shared__ptr" << std::endl; + mListener.track(sp); + } +#endif + + // non-pointer variant + template + void maybe_get_pointer(const T& t, truth) const + { + // Take the address of this object, because the object itself may be + // trackable +// add_if_trackable(boost::addressof(t)); + } + +/*==========================================================================*| + // add_if_trackable() adds LLEventTrackable objects to mTrackables + inline void add_if_trackable(const LLEventTrackable* t) const + { + if (t) + { + } + } + + // pointer to anything not an LLEventTrackable subclass + inline void add_if_trackable(const void*) const + { + } + + // pointer to free function + // The following construct uses the preprocessor to generate + // add_if_trackable() overloads accepting pointer-to-function taking + // 0, 1, ..., LLEVENTS_LISTENER_ARITY parameters of arbitrary type. +#define BOOST_PP_LOCAL_MACRO(n) \ + template \ + inline void \ + add_if_trackable(R (*)(BOOST_PP_ENUM_PARAMS(n, T))) const \ + { \ + } +#define BOOST_PP_LOCAL_LIMITS (0, LLEVENTS_LISTENER_ARITY) +#include BOOST_PP_LOCAL_ITERATE() +#undef BOOST_PP_LOCAL_MACRO +#undef BOOST_PP_LOCAL_LIMITS +|*==========================================================================*/ + + /// Bind a reference to the LLEventListener to call its track() method. + LLEventListener& mListener; + }; + + /** + * Utility template function to use Visitor appropriately + * + * @param raw_listener Callable to connect, typically a boost::bind() + * expression. This will be visited by Visitor using boost::visit_each(). + * @param connect_funct Callable that will connect() @a raw_listener to an + * LLStandardSignal, returning LLBoundListener. + */ + template + LLBoundListener visit_and_connect(const std::string& name, + const LISTENER& raw_listener, + const ConnectFunc& connect_func) + { + // Capture the listener + LLEventListener listener(raw_listener); + // Define our Visitor, binding the listener so we can call + // listener.track() if we discover any shared_ptr. + LLEventDetail::Visitor visitor(listener); + // Allow unqualified visit_each() call for ADL + using boost::visit_each; + // Visit each component of a boost::bind() expression. Pass + // 'raw_listener', our template argument, rather than 'listener' from + // which type details have been erased. unwrap() comes from + // Boost.Signals, in case we were passed a boost::ref(). + visit_each(visitor, LLEventDetail::unwrap(raw_listener)); + // Make the connection using passed function. + LLBoundListener connection(connect_func(listener)); + // If the LISTENER is an LLListenerWrapperBase subclass, pass it the + // desired information. It's important that we pass the raw_listener + // so the compiler can make decisions based on its original type. + const LLListenerWrapperBase* lwb = + ll_template_cast(&raw_listener); + if (lwb) + { + lwb->accept_name(name); + lwb->accept_connection(connection); + } + // In any case, show new connection to caller. + return connection; + } +} // namespace LLEventDetail + +// Somewhat to my surprise, passing boost::bind(...boost::weak_ptr...) to +// listen() fails in Boost code trying to instantiate LLEventListener (i.e. +// LLStandardSignal::slot_type) because the boost::get_pointer() utility function isn't +// specialized for boost::weak_ptr. This remedies that omission. +namespace boost +{ + template + T* get_pointer(const weak_ptr& ptr) { return shared_ptr(ptr).get(); } +} + +/// Since we forbid use of listen(boost::bind(...shared_ptr...)), provide an +/// easy way to cast to the corresponding weak_ptr. +template +boost::weak_ptr weaken(const boost::shared_ptr& ptr) +{ + return boost::weak_ptr(ptr); +} + +#endif /* ! defined(LL_LLEVENTS_H) */ diff --git a/indra/llcommon/llversionserver.h b/indra/llcommon/llversionserver.h deleted file mode 100644 index bc5a6dbb8..000000000 --- a/indra/llcommon/llversionserver.h +++ /dev/null @@ -1,44 +0,0 @@ -/** - * @file llversionserver.h - * @brief - * - * $LicenseInfo:firstyear=2002&license=viewergpl$ - * - * Copyright (c) 2002-2009, Linden Research, Inc. - * - * Second Life Viewer Source Code - * The source code in this file ("Source Code") is provided by Linden Lab - * to you under the terms of the GNU General Public License, version 2.0 - * ("GPL"), unless you have obtained a separate licensing agreement - * ("Other License"), formally executed by you and Linden Lab. Terms of - * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 - * - * There are special exceptions to the terms and conditions of the GPL as - * it is applied to this Source Code. View the full text of the exception - * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception - * - * By copying, modifying or distributing this software, you acknowledge - * that you have read and understood your obligations described above, - * and agree to abide by those obligations. - * - * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO - * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, - * COMPLETENESS OR PERFORMANCE. - * $/LicenseInfo$ - */ - -#ifndef LL_LLVERSIONSERVER_H -#define LL_LLVERSIONSERVER_H - -const S32 LL_VERSION_MAJOR = 1; -const S32 LL_VERSION_MINOR = 27; -const S32 LL_VERSION_PATCH = 0; -const S32 LL_VERSION_BUILD = 132845; - -const char * const LL_CHANNEL = "Second Life Server"; - - -#endif diff --git a/indra/llcommon/llversionviewer.h b/indra/llcommon/llversionviewer.h.in similarity index 89% rename from indra/llcommon/llversionviewer.h rename to indra/llcommon/llversionviewer.h.in index f8dd67fb9..fe3f1012a 100644 --- a/indra/llcommon/llversionviewer.h +++ b/indra/llcommon/llversionviewer.h.in @@ -36,12 +36,13 @@ const S32 LL_VERSION_MAJOR = 1; const S32 LL_VERSION_MINOR = 6; const S32 LL_VERSION_PATCH = 0; -const S32 LL_VERSION_BUILD = 3; +const S32 LL_VERSION_BUILD = ${vBUILD}; -const char * const LL_CHANNEL = "Singularity"; +const char * const LL_CHANNEL = "${VIEWER_CHANNEL}"; #if LL_DARWIN -const char * const LL_VERSION_BUNDLE_ID = "com.secondlife.singularity.viewer"; +const char * const LL_VERSION_BUNDLE_ID = "org.singularityviewer.singularity"; #endif #endif + diff --git a/indra/llimagej2coj/llimagej2coj.cpp b/indra/llimagej2coj/llimagej2coj.cpp index 23f67919f..afcd914f4 100644 --- a/indra/llimagej2coj/llimagej2coj.cpp +++ b/indra/llimagej2coj/llimagej2coj.cpp @@ -168,7 +168,7 @@ BOOL LLImageJ2COJ::decodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 decod // dereference the array. if(!image || !image->numcomps) { - llwarns << "ERROR -> decodeImpl: failed to decode image!" << llendl; + llwarns << "failed to decode image!" << llendl; if (image) { opj_image_destroy(image); @@ -183,6 +183,7 @@ BOOL LLImageJ2COJ::decodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 decod { if (image->comps[i].factor != base.getRawDiscardLevel()) { + llwarns << "Expected discard level not reached!" << llendl; // if we didn't get the discard level we're expecting, fail opj_image_destroy(image); base.decodeFailed(); diff --git a/indra/llinventory/llsaleinfo.cpp b/indra/llinventory/llsaleinfo.cpp index b7afb28ad..e51e35e6e 100644 --- a/indra/llinventory/llsaleinfo.cpp +++ b/indra/llinventory/llsaleinfo.cpp @@ -30,8 +30,8 @@ * $/LicenseInfo$ */ -#include #include "linden_common.h" +#include #include "llsaleinfo.h" diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index 6cc129166..8dd62f97e 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -65,6 +65,7 @@ set(llmessage_SOURCE_FILES llregionpresenceverifier.cpp llsdappservices.cpp llsdhttpserver.cpp + llsdmessage.cpp llsdmessagebuilder.cpp llsdmessagereader.cpp llsdrpcclient.cpp @@ -163,6 +164,7 @@ set(llmessage_HEADER_FILES llregionpresenceverifier.h llsdappservices.h llsdhttpserver.h + llsdmessage.h llsdmessagebuilder.h llsdmessagereader.h llsdrpcclient.h diff --git a/indra/llmessage/llregionflags.h b/indra/llmessage/llregionflags.h index 232478577..7b796a0fa 100644 --- a/indra/llmessage/llregionflags.h +++ b/indra/llmessage/llregionflags.h @@ -2,31 +2,25 @@ * @file llregionflags.h * @brief Flags that are sent in the statistics message region_flags field. * - * $LicenseInfo:firstyear=2002&license=viewergpl$ - * - * Copyright (c) 2002-2009, Linden Research, Inc. - * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ * Second Life Viewer Source Code - * The source code in this file ("Source Code") is provided by Linden Lab - * to you under the terms of the GNU General Public License, version 2.0 - * ("GPL"), unless you have obtained a separate licensing agreement - * ("Other License"), formally executed by you and Linden Lab. Terms of - * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * Copyright (C) 2010, Linden Research, Inc. * - * There are special exceptions to the terms and conditions of the GPL as - * it is applied to this Source Code. View the full text of the exception - * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * 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. * - * By copying, modifying or distributing this software, you acknowledge - * that you have read and understood your obligations described above, - * and agree to abide by those obligations. + * 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. * - * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO - * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, - * COMPLETENESS OR PERFORMANCE. + * 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$ */ @@ -48,9 +42,6 @@ const U32 REGION_FLAGS_RESET_HOME_ON_TELEPORT = (1 << 3); // Does the sun move? const U32 REGION_FLAGS_SUN_FIXED = (1 << 4); -// Tax free zone (no taxes on objects, land, etc.) -const U32 REGION_FLAGS_TAX_FREE = (1 << 5); - // Can't change the terrain heightfield, even on owned parcels, // but can plant trees and grass. const U32 REGION_FLAGS_BLOCK_TERRAFORM = (1 << 6); @@ -60,17 +51,12 @@ const U32 REGION_FLAGS_BLOCK_LAND_RESELL = (1 << 7); // All content wiped once per night const U32 REGION_FLAGS_SANDBOX = (1 << 8); -const U32 REGION_FLAGS_NULL_LAYER = (1 << 9); -// const U32 REGION_FLAGS_SKIP_AGENT_ACTION = (1 << 10); -const U32 REGION_FLAGS_HARD_ALLOW_LAND_TRANSFER = (1 << 10); // Region allows land reselling -// const U32 REGION_FLAGS_SKIP_UPDATE_INTEREST_LIST= (1 << 11); -const U32 REGION_FLAGS_HARD_ALLOW_POST_CLASSIFIED = (1 << 11); // Region allows posting of classified ads const U32 REGION_FLAGS_SKIP_COLLISIONS = (1 << 12); // Pin all non agent rigid bodies const U32 REGION_FLAGS_SKIP_SCRIPTS = (1 << 13); const U32 REGION_FLAGS_SKIP_PHYSICS = (1 << 14); // Skip all physics const U32 REGION_FLAGS_EXTERNALLY_VISIBLE = (1 << 15); -//const U32 REGION_FLAGS_MAINLAND_VISIBLE = (1 << 16); -const U32 REGION_FLAGS_PUBLIC_ALLOWED = (1 << 17); +const U32 REGION_FLAGS_ALLOW_RETURN_ENCROACHING_OBJECT = (1 << 16); +const U32 REGION_FLAGS_ALLOW_RETURN_ENCROACHING_ESTATE_OBJECT = (1 << 17); const U32 REGION_FLAGS_BLOCK_DWELL = (1 << 18); // Is flight allowed? @@ -87,18 +73,13 @@ const U32 REGION_FLAGS_ESTATE_SKIP_SCRIPTS = (1 << 21); const U32 REGION_FLAGS_RESTRICT_PUSHOBJECT = (1 << 22); const U32 REGION_FLAGS_DENY_ANONYMOUS = (1 << 23); -// const U32 REGION_FLAGS_DENY_IDENTIFIED = (1 << 24); -// const U32 REGION_FLAGS_DENY_TRANSACTED = (1 << 25); const U32 REGION_FLAGS_ALLOW_PARCEL_CHANGES = (1 << 26); -const U32 REGION_FLAGS_ABUSE_EMAIL_TO_ESTATE_OWNER = (1 << 27); - const U32 REGION_FLAGS_ALLOW_VOICE = (1 << 28); const U32 REGION_FLAGS_BLOCK_PARCEL_SEARCH = (1 << 29); const U32 REGION_FLAGS_DENY_AGEUNVERIFIED = (1 << 30); -const U32 REGION_FLAGS_SKIP_MONO_SCRIPTS = (1 << 31); const U32 REGION_FLAGS_DEFAULT = REGION_FLAGS_ALLOW_LANDMARK | REGION_FLAGS_ALLOW_SET_HOME | @@ -111,7 +92,6 @@ const U32 REGION_FLAGS_PRELUDE_UNSET = REGION_FLAGS_ALLOW_LANDMARK | REGION_FLAGS_ALLOW_SET_HOME; const U32 REGION_FLAGS_ESTATE_MASK = REGION_FLAGS_EXTERNALLY_VISIBLE - | REGION_FLAGS_PUBLIC_ALLOWED | REGION_FLAGS_SUN_FIXED | REGION_FLAGS_DENY_ANONYMOUS | REGION_FLAGS_DENY_AGEUNVERIFIED; diff --git a/indra/llmessage/llsdmessage.cpp b/indra/llmessage/llsdmessage.cpp new file mode 100644 index 000000000..9148c9dd1 --- /dev/null +++ b/indra/llmessage/llsdmessage.cpp @@ -0,0 +1,170 @@ +/** + * @file llsdmessage.cpp + * @author Nat Goodspeed + * @date 2008-10-31 + * @brief Implementation for llsdmessage. + * + * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, 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$ + */ + +#if LL_WINDOWS +#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want! +#endif + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llsdmessage.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "llevents.h" +#include "llsdserialize.h" +#include "llhttpclient.h" +#include "llmessageconfig.h" +#include "llhost.h" +#include "message.h" +#include "llsdutil.h" + +// Declare a static LLSDMessage instance to ensure that we have a listener as +// soon as someone tries to post on our canonical LLEventPump name. +static LLSDMessage httpListener; + +LLSDMessage::LLSDMessage(): + // Instantiating our own local LLEventPump with a string name the + // constructor is NOT allowed to tweak is a way of ensuring Singleton + // semantics: attempting to instantiate a second LLSDMessage object would + // throw LLEventPump::DupPumpName. + mEventPump("LLHTTPClient") +{ + mEventPump.listen("self", boost::bind(&LLSDMessage::httpListener, this, _1)); +} + +bool LLSDMessage::httpListener(const LLSD& request) +{ + // Extract what we want from the request object. We do it all up front + // partly to document what we expect. + LLSD::String url(request["url"]); + LLSD payload(request["payload"]); + LLSD::String reply(request["reply"]); + LLSD::String error(request["error"]); + LLSD::Real timeout(request["timeout"]); + // If the LLSD doesn't even have a "url" key, we doubt it was intended for + // this listener. + if (url.empty()) + { + std::ostringstream out; + out << "request event without 'url' key to '" << mEventPump.getName() << "'"; + throw ArgError(out.str()); + } + // Establish default timeout. This test relies on LLSD::asReal() returning + // exactly 0.0 for an undef value. + if (! timeout) + { + timeout = HTTP_REQUEST_EXPIRY_SECS; + } + LLHTTPClient::post(url, payload, + new LLSDMessage::EventResponder(LLEventPumps::instance(), + request, + url, "POST", reply, error), + LLSD(), // headers + timeout); + return false; +} + +void LLSDMessage::EventResponder::result(const LLSD& data) +{ + // If our caller passed an empty replyPump name, they're not + // listening: this is a fire-and-forget message. Don't bother posting + // to the pump whose name is "". + if (! mReplyPump.empty()) + { + LLSD response(data); + mReqID.stamp(response); + mPumps.obtain(mReplyPump).post(response); + } + else // default success handling + { + LL_INFOS("LLSDMessage::EventResponder") + << "'" << mMessage << "' to '" << mTarget << "' succeeded" + << LL_ENDL; + } +} + +void LLSDMessage::EventResponder::errorWithContent(U32 status, const std::string& reason, const LLSD& content) +{ + // If our caller passed an empty errorPump name, they're not + // listening: "default error handling is acceptable." Only post to an + // explicit pump name. + if (! mErrorPump.empty()) + { + LLSD info(mReqID.makeResponse()); + info["target"] = mTarget; + info["message"] = mMessage; + info["status"] = LLSD::Integer(status); + info["reason"] = reason; + info["content"] = content; + mPumps.obtain(mErrorPump).post(info); + } + else // default error handling + { + // convention seems to be to use llinfos, but that seems a bit casual? + LL_WARNS("LLSDMessage::EventResponder") + << "'" << mMessage << "' to '" << mTarget + << "' failed with code " << status << ": " << reason << '\n' + << ll_pretty_print_sd(content) + << LL_ENDL; + } +} + +LLSDMessage::ResponderAdapter::ResponderAdapter(LLHTTPClient::ResponderPtr responder, + const std::string& name): + mResponder(responder), + mReplyPump(name + ".reply", true), // tweak name for uniqueness + mErrorPump(name + ".error", true) +{ + mReplyPump.listen("self", boost::bind(&ResponderAdapter::listener, this, _1, true)); + mErrorPump.listen("self", boost::bind(&ResponderAdapter::listener, this, _1, false)); +} + +bool LLSDMessage::ResponderAdapter::listener(const LLSD& payload, bool success) +{ + if (success) + { + mResponder->result(payload); + } + else + { + mResponder->errorWithContent(payload["status"].asInteger(), payload["reason"], payload["content"]); + } + + /*---------------- MUST BE LAST STATEMENT BEFORE RETURN ----------------*/ + delete this; + // Destruction of mResponder will usually implicitly free its referent as well + /*------------------------- NOTHING AFTER THIS -------------------------*/ + return false; +} + +void LLSDMessage::link() +{ +} diff --git a/indra/llmessage/llsdmessage.h b/indra/llmessage/llsdmessage.h new file mode 100644 index 000000000..0d34847ff --- /dev/null +++ b/indra/llmessage/llsdmessage.h @@ -0,0 +1,166 @@ +/** + * @file llsdmessage.h + * @author Nat Goodspeed + * @date 2008-10-30 + * @brief API intended to unify sending capability, UDP and TCP messages: + * https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes + * + * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, 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$ + */ + +#if ! defined(LL_LLSDMESSAGE_H) +#define LL_LLSDMESSAGE_H + +#include "llerror.h" // LOG_CLASS() +#include "llevents.h" // LLEventPumps +#include "llhttpclient.h" +#include +#include + +class LLSD; + +/** + * Class managing the messaging API described in + * https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes + */ +class LLSDMessage +{ + LOG_CLASS(LLSDMessage); + +public: + LLSDMessage(); + + /// Exception if you specify arguments badly + struct ArgError: public std::runtime_error + { + ArgError(const std::string& what): + std::runtime_error(std::string("ArgError: ") + what) {} + }; + + /** + * The response idiom used by LLSDMessage -- LLEventPump names on which to + * post reply or error -- is designed for the case in which your + * reply/error handlers are methods on the same class as the method + * sending the message. Any state available to the sending method that + * must be visible to the reply/error methods can conveniently be stored + * on that class itself, if it's not already. + * + * The LLHTTPClient::Responder idiom requires a separate instance of a + * separate class so that it can dispatch to the code of interest by + * calling canonical virtual methods. Interesting state must be copied + * into that new object. + * + * With some trepidation, because existing response code is packaged in + * LLHTTPClient::Responder subclasses, we provide this adapter class + * for transitional purposes only. Instantiate a new heap + * ResponderAdapter with your new LLHTTPClient::ResponderPtr. Pass + * ResponderAdapter::getReplyName() and/or getErrorName() in your + * LLSDMessage (or LLViewerRegion::getCapAPI()) request event. The + * ResponderAdapter will call the appropriate Responder method, then + * @c delete itself. + */ + class ResponderAdapter + { + public: + /** + * Bind the new LLHTTPClient::Responder subclass instance. + * + * Passing the constructor a name other than the default is only + * interesting if you suspect some usage will lead to an exception or + * log message. + */ + ResponderAdapter(LLHTTPClient::ResponderPtr responder, + const std::string& name="ResponderAdapter"); + + /// EventPump name on which LLSDMessage should post reply event + std::string getReplyName() const { return mReplyPump.getName(); } + /// EventPump name on which LLSDMessage should post error event + std::string getErrorName() const { return mErrorPump.getName(); } + + private: + // We have two different LLEventStreams, though we route them both to + // the same listener, so that we can bind an extra flag identifying + // which case (reply or error) reached that listener. + bool listener(const LLSD&, bool success); + + LLHTTPClient::ResponderPtr mResponder; + LLEventStream mReplyPump, mErrorPump; + }; + + /** + * Force our implementation file to be linked with caller. The .cpp file + * contains a static instance of this class, which must be linked into the + * executable to support the canonical listener. But since the primary + * interface to that static instance is via a named LLEventPump rather + * than by direct reference, the linker doesn't necessarily perceive the + * necessity to bring in the translation unit. Referencing this dummy + * method forces the issue. + */ + static void link(); + +private: + friend class LLCapabilityListener; + /// Responder used for internal purposes by LLSDMessage and + /// LLCapabilityListener. Others should use higher-level APIs. + class EventResponder: public LLHTTPClient::Responder + { + public: + /** + * LLHTTPClient::Responder that dispatches via named LLEventPump instances. + * We bind LLEventPumps, even though it's an LLSingleton, for testability. + * We bind the string names of the desired LLEventPump instances rather + * than actually obtain()ing them so we only obtain() the one we're going + * to use. If the caller doesn't bother to listen() on it, the other pump + * may never materialize at all. + * @a target and @a message are only to clarify error processing. + * For a capability message, @a target should be the region description, + * @a message should be the capability name. + * For a service with a visible URL, pass the URL as @a target and the HTTP verb + * (e.g. "POST") as @a message. + */ + EventResponder(LLEventPumps& pumps, + const LLSD& request, + const std::string& target, const std::string& message, + const std::string& replyPump, const std::string& errorPump): + mPumps(pumps), + mReqID(request), + mTarget(target), + mMessage(message), + mReplyPump(replyPump), + mErrorPump(errorPump) + {} + + virtual void result(const LLSD& data); + virtual void errorWithContent(U32 status, const std::string& reason, const LLSD& content); + + private: + LLEventPumps& mPumps; + LLReqID mReqID; + const std::string mTarget, mMessage, mReplyPump, mErrorPump; + }; + +private: + bool httpListener(const LLSD&); + LLEventStream mEventPump; +}; + +#endif /* ! defined(LL_LLSDMESSAGE_H) */ diff --git a/indra/llrender/llfontgl.cpp b/indra/llrender/llfontgl.cpp index 2148f6d94..286c5b3a5 100644 --- a/indra/llrender/llfontgl.cpp +++ b/indra/llrender/llfontgl.cpp @@ -1141,7 +1141,7 @@ LLFontGL* LLFontGL::getFontSansSerifBig() //static LLFontGL* LLFontGL::getFontSansSerifHuge() { - static LLFontGL* fontp = getFont(LLFontDescriptor("SansSerif","Large",0)); + static LLFontGL* fontp = getFont(LLFontDescriptor("SansSerif","Huge",0)); return fontp; } @@ -1320,4 +1320,4 @@ void LLFontGL::drawGlyph(const LLRectf& screen_rect, const LLRectf& uv_rect, con } gGL.end(); -} \ No newline at end of file +} diff --git a/indra/llrender/llshadermgr.cpp b/indra/llrender/llshadermgr.cpp index 8a9f15d13..e17fd6998 100644 --- a/indra/llrender/llshadermgr.cpp +++ b/indra/llrender/llshadermgr.cpp @@ -699,7 +699,7 @@ GLhandleARB LLShaderMgr::loadShaderFile(const std::string& filename, S32 & shade text[count++] = strdup("return texture2D(tex0, texcoord);\n"); text[count++] = strdup("}\n"); } - else if (gGLManager.mGLVersion >= 3.f) + else if (gGLManager.mGLVersion >= 3.f && !(gGLManager.mIsATI && gGLManager.mGLVersion < 3.3f) ) { text[count++] = strdup("\tswitch (int(vary_texture_index+0.25))\n"); text[count++] = strdup("\t{\n"); diff --git a/indra/llui/llmemberlistener.h b/indra/llui/llmemberlistener.h index a5e019c33..0760213c7 100644 --- a/indra/llui/llmemberlistener.h +++ b/indra/llui/llmemberlistener.h @@ -62,7 +62,7 @@ #include "llevent.h" template -class LLMemberListener : public LLSimpleListener +class LLMemberListener : public LLOldEvents::LLSimpleListener { public: LLMemberListener() : mPtr(NULL), mRegisteredName() { } @@ -75,7 +75,7 @@ public: } // This is what you have to override to handle this event - virtual bool handleEvent(LLPointer event, const LLSD& userdata) = 0; + virtual bool handleEvent(LLPointer event, const LLSD& userdata) = 0; protected: T *mPtr; // The object that this listener manipulates diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index 6b04cb91c..849099c62 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -67,6 +67,8 @@ #include #include +using namespace LLOldEvents; + // static LLMenuHolderGL *LLMenuGL::sMenuContainer = NULL; diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h index b8a5f614d..422642276 100644 --- a/indra/llui/llmenugl.h +++ b/indra/llui/llmenugl.h @@ -263,7 +263,7 @@ public: // calls a user defined callback. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLMenuItemCallGL : public LLMenuItemGL, public LLObservable +class LLMenuItemCallGL : public LLMenuItemGL, public LLOldEvents::LLObservable { public: // normal constructor diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index c513b82f9..aac44ddf1 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -100,43 +100,12 @@ #include "llinstancetracker.h" // and we need this to manage the notification callbacks +#include "llevents.h" #include "llfunctorregistry.h" #include "llui.h" #include "llxmlnode.h" #include "llnotificationptr.h" - -/***************************************************************************** -* Signal and handler declarations -* Using a single handler signature means that we can have a common handler -* type, rather than needing a distinct one for each different handler. -*****************************************************************************/ - -/** - * A boost::signals2 Combiner that stops the first time a handler returns true - * We need this because we want to have our handlers return bool, so that - * we have the option to cause a handler to stop further processing. The - * default handler fails when the signal returns a value but has no slots. - */ -struct LLStopWhenHandled -{ - typedef bool result_type; - - template - result_type operator()(InputIterator first, InputIterator last) const - { - for (InputIterator si = first; si != last; ++si) - { - if (*si) - { - return true; - } - } - return false; - } -}; - - typedef enum e_notification_priority { NOTIFICATION_PRIORITY_UNSPECIFIED, @@ -146,27 +115,11 @@ typedef enum e_notification_priority NOTIFICATION_PRIORITY_CRITICAL } ENotificationPriority; -/** - * We want to have a standard signature for all signals; this way, - * we can easily document a protocol for communicating across - * dlls and into scripting languages someday. - * we want to return a bool to indicate whether the signal has been - * handled and should NOT be passed on to other listeners. - * Return true to stop further handling of the signal, and false - * to continue. - * We take an LLSD because this way the contents of the signal - * are independent of the API used to communicate it. - * It is const ref because then there's low cost to pass it; - * if you only need to inspect it, it's very cheap. - */ - typedef boost::function LLNotificationResponder; typedef LLFunctorRegistry LLNotificationFunctorRegistry; typedef LLFunctorRegistration LLNotificationFunctorRegistration; -typedef boost::signals2::signal LLStandardSignal; - // context data that can be looked up via a notification's payload by the display logic // derive from this class to implement specific contexts class LLNotificationContext : public LLInstanceTracker diff --git a/indra/llui/lltrans.cpp b/indra/llui/lltrans.cpp index 780d204bf..f7988c965 100644 --- a/indra/llui/lltrans.cpp +++ b/indra/llui/lltrans.cpp @@ -117,6 +117,69 @@ std::string LLTrans::getString(const std::string &xml_desc, const LLStringUtil:: } } +//static +std::string LLTrans::getString(const std::string &xml_desc, const LLSD& msg_args) +{ + // Don't care about time as much as call count. Make sure we're not + // calling LLTrans::getString() in an inner loop. JC + //V3: LLFastTimer timer(FTM_GET_TRANS); + + template_map_t::iterator iter = sStringTemplates.find(xml_desc); + if (iter != sStringTemplates.end()) + { + std::string text = iter->second.mText; + LLStringUtil::format(text, msg_args); + return text; + } + else + { + LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL; + return "MissingString("+xml_desc+")"; + } +} + +//static +bool LLTrans::findString(std::string &result, const std::string &xml_desc, const LLStringUtil::format_map_t& msg_args) +{ + //V3: LLFastTimer timer(FTM_GET_TRANS); + + template_map_t::iterator iter = sStringTemplates.find(xml_desc); + if (iter != sStringTemplates.end()) + { + std::string text = iter->second.mText; + LLStringUtil::format_map_t args = sDefaultArgs; + args.insert(msg_args.begin(), msg_args.end()); + LLStringUtil::format(text, args); + result = text; + return true; + } + else + { + LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL; + return false; + } +} + +//static +bool LLTrans::findString(std::string &result, const std::string &xml_desc, const LLSD& msg_args) +{ + //V3: LLFastTimer timer(FTM_GET_TRANS); + + template_map_t::iterator iter = sStringTemplates.find(xml_desc); + if (iter != sStringTemplates.end()) + { + std::string text = iter->second.mText; + LLStringUtil::format(text, msg_args); + result = text; + return true; + } + else + { + LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL; + return false; + } +} + void LLTrans::setDefaultArg(const std::string& name, const std::string& value) { sDefaultArgs[name] = value; diff --git a/indra/llui/lltrans.h b/indra/llui/lltrans.h index 56ed08f3b..b997d6954 100644 --- a/indra/llui/lltrans.h +++ b/indra/llui/lltrans.h @@ -60,12 +60,14 @@ public: /** * @brief Parses the xml file that holds the strings. Used once on startup - * @param xml_filename Filename to parse +// *FIXME * @param xml_filename Filename to parse * @param default_args Set of strings (expected to be in the file) to use as default replacement args, e.g. "SECOND_LIFE" * @returns true if the file was parsed successfully, true if something went wrong */ static bool parseStrings(const std::string& xml_filename, const std::set& default_args); + //V3: static bool parseLanguageStrings(LLPointer & root); + /** * @brief Returns a translated string * @param xml_desc String's description @@ -73,6 +75,9 @@ public: * @returns Translated string */ static std::string getString(const std::string &xml_desc, const LLStringUtil::format_map_t& args); + static std::string getString(const std::string &xml_desc, const LLSD& args); + static bool findString(std::string &result, const std::string &xml_desc, const LLStringUtil::format_map_t& args); + static bool findString(std::string &result, const std::string &xml_desc, const LLSD& args); /** * @brief Returns a translated string @@ -84,7 +89,20 @@ public: LLStringUtil::format_map_t empty; return getString(xml_desc, empty); } - + + static bool findString(std::string &result, const std::string &xml_desc) + { + LLStringUtil::format_map_t empty; + return findString(result, xml_desc, empty); + } + + static std::string getKeyboardString(const char* keystring) + { + std::string key_str(keystring); + std::string trans_str; + return findString(trans_str, key_str) ? trans_str : key_str; + } + // get the default args static const LLStringUtil::format_map_t& getDefaultArgs() { diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp index b1a730c73..a427346ed 100644 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -63,6 +63,8 @@ #include "lldelayeduidelete.h" // +using namespace LLOldEvents; + //HACK: this allows you to instantiate LLView from xml with "" which we don't want static LLRegisterWidget r("view"); diff --git a/indra/llui/llview.h b/indra/llui/llview.h index acde9bf22..676e21a2a 100644 --- a/indra/llui/llview.h +++ b/indra/llui/llview.h @@ -138,7 +138,7 @@ virtual void setControlName(const std::string& control, LLView *context); LLSliderCtrl, LLCheckBoxCtrl virtual std::string getControlName() const { return mControlName; } LLSliderCtrl, LLCheckBoxCtrl -virtual bool handleEvent(LLPointer event, const LLSD& userdata); +virtual bool handleEvent(LLPointer event, const LLSD& userdata); LLMenuItem virtual void setValue(const LLSD& value); * @@ -449,15 +449,15 @@ public: void localRectToScreen( const LLRect& local, LLRect* screen ) const; // Listener dispatching functions (Dispatcher deletes pointers to listeners on deregistration or destruction) - LLSimpleListener* getListenerByName(const std::string& callback_name); - void registerEventListener(std::string name, LLSimpleListener* function); + LLOldEvents::LLSimpleListener* getListenerByName(const std::string& callback_name); + void registerEventListener(std::string name, LLOldEvents::LLSimpleListener* function); void deregisterEventListener(std::string name); - typedef boost::signals2::signal event, const LLSD& userdata)> event_signal_t; + typedef boost::signals2::signal event, const LLSD& userdata)> event_signal_t; void registerEventListener(std::string name, event_signal_t::slot_type &cb); - std::string findEventListener(LLSimpleListener *listener) const; - void addListenerToControl(LLEventDispatcher *observer, const std::string& name, LLSD filter, LLSD userdata); + std::string findEventListener(LLOldEvents::LLSimpleListener *listener) const; + void addListenerToControl(LLOldEvents::LLEventDispatcher *observer, const std::string& name, LLSD filter, LLSD userdata); void addBoolControl(const std::string& name, bool initial_value); LLControlVariable *getControl(const std::string& name); @@ -466,7 +466,7 @@ public: bool setControlValue(const LLSD& value); virtual void setControlName(const std::string& control, LLView *context); virtual std::string getControlName() const { return mControlName; } -// virtual bool handleEvent(LLPointer event, const LLSD& userdata); +// virtual bool handleEvent(LLPointer event, const LLSD& userdata); virtual void setValue(const LLSD& value); virtual LLSD getValue() const; @@ -676,7 +676,7 @@ private: static LLWindow* sWindow; // All root views must know about their window. - typedef std::map > dispatch_list_t; + typedef std::map > dispatch_list_t; dispatch_list_t mDispatchList; std::string mControlName; diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index b87cb21a7..e21b9dfe4 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -1723,7 +1723,7 @@ void LLWindowSDL::processMiscNativeEvents() void LLWindowSDL::gatherInput() { - const Uint32 CLICK_THRESHOLD = 300; // milliseconds + const Uint32 CLICK_THRESHOLD = 500; // milliseconds static int leftClick = 0; static int rightClick = 0; static Uint32 lastLeftDown = 0; diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index ea59c495f..cf3f2a06f 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -19,7 +19,7 @@ endif(NOT FMODEX) include(OPENAL) include(HUNSPELL) include(FindOpenGL) -include(JsonCpp) +#include(JsonCpp) include(LLAddBuildTest) include(LLAudio) include(LLCharacter) @@ -76,6 +76,8 @@ include_directories( ) set(viewer_SOURCE_FILES + sgmemstat.cpp + sgversion.cpp llviewerobjectbackup.cpp slfloatermediafilter.cpp floaterlocalassetbrowse.cpp @@ -134,6 +136,7 @@ set(viewer_SOURCE_FILES llbuildnewviewsscheduler.cpp llcallbacklist.cpp llcallingcard.cpp + llcapabilitylistener.cpp llcaphttpsender.cpp llchatbar.cpp llclassifiedinfo.cpp @@ -445,7 +448,6 @@ set(viewer_SOURCE_FILES lltoolselectrect.cpp lltoolview.cpp lltracker.cpp - lltranslate.cpp lluploaddialog.cpp lluploadfloaterobservers.cpp llurl.cpp @@ -496,6 +498,7 @@ set(viewer_SOURCE_FILES llviewerregion.cpp llviewershadermgr.cpp llviewerstats.cpp + llviewerstatsrecorder.cpp llviewertexteditor.cpp llviewertexture.cpp llviewertextureanim.cpp @@ -567,6 +570,8 @@ set(viewer_HEADER_FILES CMakeLists.txt ViewerInstall.cmake + sgmemstat.h + sgversion.h llviewerobjectbackup.h slfloatermediafilter.h floaterlocalassetbrowse.h @@ -624,6 +629,7 @@ set(viewer_HEADER_FILES llbuildnewviewsscheduler.h llcallbacklist.h llcallingcard.h + llcapabilitylistener.h llcaphttpsender.h llchatbar.h llclassifiedinfo.h @@ -648,7 +654,6 @@ set(viewer_HEADER_FILES lldrawpoolalpha.h lldrawpoolavatar.h lldrawpoolbump.h - lldrawpoolclouds.h lldrawpoolground.h lldrawpoolsimple.h lldrawpoolsky.h @@ -955,7 +960,6 @@ set(viewer_HEADER_FILES llviewerassetstorage.h llviewerassettype.h llvieweraudio.h - llviewerbuild.h llviewercamera.h llviewercontrol.h llviewerdisplay.h @@ -992,6 +996,7 @@ set(viewer_HEADER_FILES llviewerregion.h llviewershadermgr.h llviewerstats.h + llviewerstatsrecorder.h llviewertexteditor.h llviewertexture.h llviewertextureanim.h @@ -1358,12 +1363,16 @@ if (FMOD OR FMODEX) if (NOT WINDOWS) set(fmodwrapper_SOURCE_FILES fmodwrapper.cpp) add_library(fmodwrapper SHARED ${fmodwrapper_SOURCE_FILES}) - if(FMODEX) - set(fmodwrapper_needed_LIBRARIES ${FMODEX_LIBRARY}) - endif(FMODEX) - if(FMOD) - set(fmodwrapper_needed_LIBRARIES "${fmodwrapper_needed_LIBRARIES} ${FMOD_LIBRARY}") - endif(FMOD) + if(FMOD AND FMODEX) + set(fmodwrapper_needed_LIBRARIES "${FMODEX_LIBRARY} ${FMOD_LIBRARY}") + else(FMOD AND FMODEX) + if(FMODEX) + set(fmodwrapper_needed_LIBRARIES ${FMODEX_LIBRARY}) + endif(FMODEX) + if(FMOD) + set(fmodwrapper_needed_LIBRARIES "${FMOD_LIBRARY}") + endif(FMOD) + endif(FMOD AND FMODEX) if (DARWIN) list(APPEND fmodwrapper_needed_LIBRARIES ${CARBON_LIBRARY}) set_target_properties( diff --git a/indra/newview/app_settings/default_grids.xml b/indra/newview/app_settings/default_grids.xml index 5d8c2a088..a909642b8 100755 --- a/indra/newview/app_settings/default_grids.xml +++ b/indra/newview/app_settings/default_grids.xml @@ -2,15 +2,16 @@ - default_grids_version2 + default_grids_version20 + gridnicksecondlife gridnameSecond Life platformSecondLife loginurihttps://login.agni.lindenlab.com/cgi-bin/login.cgi - loginpagehttp://viewer-login.agni.lindenlab.com/ + loginpagehttp://viewer-login.agni.lindenlab.com/ helperurihttps://secondlife.com/helpers/ websitehttp://secondlife.com/ supporthttp://secondlife.com/support/ @@ -18,30 +19,34 @@ passwordhttp://secondlife.com/account/request.php render_compat1 inventory_links1 + auto_update0 + gridnicksecondlife_beta gridnameSecond Life BETA helperurihttp://aditi-secondlife.webdev.lindenlab.com/helpers/ - loginpagehttp://viewer-login.agni.lindenlab.com/ + loginpagehttp://viewer-login.agni.lindenlab.com/ loginurihttps://login.aditi.lindenlab.com/cgi-bin/login.cgi passwordhttp://secondlife.com/account/request.php platformSecondLife + websitehttp://secondlife.com/ + supporthttp://secondlife.com/support/ registerhttp://secondlife.com/registration/ render_compat0 inventory_links1 - supporthttp://secondlife.com/support/ - version0 - websitehttp://secondlife.com/ + auto_update0 + gridnicklocal gridnameLocal Host platformOpenSim loginurihttp://127.0.0.1:9000/ helperurihttp://127.0.0.1:9000/ + auto_update0 diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 6468e8ce3..9dc288958 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -8,6 +8,30 @@ settings_sh.xml settings_rlv.xml + + SianaRenderDeferredInvisiprim + + Comment + Support invisiprims in deferred mode + Persist + 1 + Type + Boolean + Value + 1 + + + SGAbsolutePointer + + Comment + Support pen tablets and absolute pointer devices by disabling mouse wrapping + Persist + 1 + Type + Boolean + Value + 0 + WaterPresetName @@ -4225,6 +4249,17 @@ Value -1 + DebugStatModeMalloc + + Comment + Mode of stat in Statistics floater + Persist + 1 + Type + S32 + Value + -1 + DebugStatModeFormattedMem Comment @@ -8198,7 +8233,7 @@ Type Boolean Value - 1 + 0 MemoryPrivatePoolSize diff --git a/indra/newview/app_settings/settings_ascent.xml b/indra/newview/app_settings/settings_ascent.xml index ec2b81116..cab03f74f 100644 --- a/indra/newview/app_settings/settings_ascent.xml +++ b/indra/newview/app_settings/settings_ascent.xml @@ -84,7 +84,7 @@ AscentAutoCloseOOC Comment - Auto-close OOC chat (i.e. add \"))\" if not found and \"((\" was used) + Auto-close OOC chat (i.e. add "))" if not found and "((" was used) Persist 1 Type @@ -150,7 +150,7 @@ AscentShowSelfTagColor Comment - Show your own tag + Show your own tagcolor to yourself(instead of default linden viewer color). Persist 1 Type @@ -293,7 +293,7 @@ AscentShowIdleTime Comment - Show client tags for others. + Show idle time of others in tags. Persist 1 Type @@ -403,7 +403,7 @@ AscentBuildPrefs_PivotIsPercent Comment - Would you like the chatbar to be able to be used for command line functions? + Pivot points are based on percentages Persist 1 Type @@ -414,7 +414,7 @@ AscentBuildPrefs_PivotX Comment - idfk + Pivot point on X-axis for new objects Persist 1 Type @@ -425,7 +425,7 @@ AscentBuildPrefs_PivotY Comment - idfk + Pivot point on Y-axis for new objects Persist 1 Type @@ -436,7 +436,7 @@ AscentBuildPrefs_PivotZ Comment - idfk + Pivot point on Z-axis for new objects Persist 1 Type @@ -620,5 +620,16 @@ Value tp2 + SinguCmdLineAway + + Comment + Toggle Fake Away Status. + Persist + 1 + Type + String + Value + /away + diff --git a/indra/newview/ascentprefssys.cpp b/indra/newview/ascentprefssys.cpp index 66a8d7e72..493bb7a69 100644 --- a/indra/newview/ascentprefssys.cpp +++ b/indra/newview/ascentprefssys.cpp @@ -71,6 +71,7 @@ LLPrefsAscentSys::LLPrefsAscentSys() childSetCommitCallback("AscentCmdLineOfferTp", onCommitCmdLine, this); childSetCommitCallback("AscentCmdLineMapTo", onCommitCmdLine, this); childSetCommitCallback("AscentCmdLineTP2", onCommitCmdLine, this); + childSetCommitCallback("SinguCmdLineAway", onCommitCmdLine, this); refreshValues(); refresh(); @@ -169,6 +170,7 @@ void LLPrefsAscentSys::onCommitCmdLine(LLUICtrl* ctrl, void* user_data) self->childSetEnabled("AscentCmdLineMapTo", enabled); self->childSetEnabled("map_to_keep_pos", enabled); self->childSetEnabled("AscentCmdLineTP2", enabled); + self->childSetEnabled("SinguCmdLineAway", enabled); } gSavedSettings.setString("AscentCmdLinePos", self->childGetValue("AscentCmdLinePos")); @@ -184,6 +186,7 @@ void LLPrefsAscentSys::onCommitCmdLine(LLUICtrl* ctrl, void* user_data) gSavedSettings.setString("AscentCmdLineOfferTp", self->childGetValue("AscentCmdLineOfferTp")); gSavedSettings.setString("AscentCmdLineMapTo", self->childGetValue("AscentCmdLineMapTo")); gSavedSettings.setString("AscentCmdLineTP2", self->childGetValue("AscentCmdLineTP2")); + gSavedSettings.setString("SinguCmdLineAway", self->childGetValue("SinguCmdLineAway")); } void LLPrefsAscentSys::refreshValues() @@ -225,12 +228,14 @@ void LLPrefsAscentSys::refreshValues() mCmdLineMapTo = gSavedSettings.getString("AscentCmdLineMapTo"); mCmdMapToKeepPos = gSavedSettings.getBOOL("AscentMapToKeepPos"); mCmdLineTP2 = gSavedSettings.getString("AscentCmdLineTP2"); + mCmdLineAway = gSavedSettings.getString("SinguCmdLineAway"); - //Privacy ----------------------------------------------------------------------------- + //Security ---------------------------------------------------------------------------- mBroadcastViewerEffects = gSavedSettings.getBOOL("BroadcastViewerEffects"); mDisablePointAtAndBeam = gSavedSettings.getBOOL("DisablePointAtAndBeam"); mPrivateLookAt = gSavedSettings.getBOOL("PrivateLookAt"); mShowLookAt = gSavedSettings.getBOOL("AscentShowLookAt"); + mQuietSnapshotsToDisk = gSavedSettings.getBOOL("QuietSnapshotsToDisk"); mRevokePermsOnStandUp = gSavedSettings.getBOOL("RevokePermsOnStandUp"); mDisableClickSit = gSavedSettings.getBOOL("DisableClickSit"); mDisplayScriptJumps = gSavedSettings.getBOOL("AscentDisplayTotalScriptJumps"); @@ -275,6 +280,7 @@ void LLPrefsAscentSys::refresh() childSetEnabled("AscentCmdLineMapTo", mCmdLine); childSetEnabled("map_to_keep_pos", mCmdLine); childSetEnabled("AscentCmdLineTP2", mCmdLine); + childSetEnabled("SinguCmdLineAway", mCmdLine); childSetValue("AscentCmdLinePos", mCmdLinePos); childSetValue("AscentCmdLineGround", mCmdLineGround); @@ -289,6 +295,7 @@ void LLPrefsAscentSys::refresh() childSetValue("AscentCmdLineOfferTp", mCmdLineOfferTp); childSetValue("AscentCmdLineMapTo", mCmdLineMapTo); childSetValue("AscentCmdLineTP2", mCmdLineTP2); + childSetValue("SinguCmdLineAway", mCmdLineAway); } void LLPrefsAscentSys::cancel() @@ -329,12 +336,14 @@ void LLPrefsAscentSys::cancel() gSavedSettings.setString("AscentCmdLineMapTo", mCmdLineMapTo); gSavedSettings.setBOOL("AscentMapToKeepPos", mCmdMapToKeepPos); gSavedSettings.setString("AscentCmdLineTP2", mCmdLineTP2); + gSavedSettings.setString("SinguCmdLineAway", mCmdLineAway); - //Privacy ----------------------------------------------------------------------------- + //Security ---------------------------------------------------------------------------- gSavedSettings.setBOOL("BroadcastViewerEffects", mBroadcastViewerEffects); gSavedSettings.setBOOL("DisablePointAtAndBeam", mDisablePointAtAndBeam); gSavedSettings.setBOOL("PrivateLookAt", mPrivateLookAt); gSavedSettings.setBOOL("AscentShowLookAt", mShowLookAt); + gSavedSettings.setBOOL("QuietSnapshotsToDisk", mQuietSnapshotsToDisk); gSavedSettings.setBOOL("RevokePermsOnStandUp", mRevokePermsOnStandUp); gSavedSettings.setBOOL("DisableClickSit", mDisableClickSit); gSavedSettings.setBOOL("AscentDisplayTotalScriptJumps", mDisplayScriptJumps); diff --git a/indra/newview/ascentprefssys.h b/indra/newview/ascentprefssys.h index 0137ddd29..c31751574 100644 --- a/indra/newview/ascentprefssys.h +++ b/indra/newview/ascentprefssys.h @@ -88,13 +88,15 @@ protected: std::string mCmdLineMapTo; BOOL mCmdMapToKeepPos; std::string mCmdLineTP2; + std::string mCmdLineAway; - //Privacy ----------------------------------------------------------------------------- + //Security ---------------------------------------------------------------------------- BOOL mBroadcastViewerEffects; BOOL mDisablePointAtAndBeam; BOOL mPrivateLookAt; BOOL mShowLookAt; - BOOL mRevokePermsOnStandUp; + BOOL mQuietSnapshotsToDisk; + BOOL mRevokePermsOnStandUp; BOOL mDisableClickSit; BOOL mDisplayScriptJumps; F32 mNumScriptDiff; diff --git a/indra/newview/chatbar_as_cmdline.cpp b/indra/newview/chatbar_as_cmdline.cpp index 4539b3a34..a0051de59 100644 --- a/indra/newview/chatbar_as_cmdline.cpp +++ b/indra/newview/chatbar_as_cmdline.cpp @@ -55,6 +55,7 @@ #include "llfloateravatarlist.h" #include "llviewerobjectlist.h" #include "llviewertexteditor.h" +#include "llviewermenu.h" #include "llvoavatar.h" #include "lltooldraganddrop.h" #include "llinventorymodel.h" @@ -398,6 +399,10 @@ bool cmd_line_chat(std::string revised_text, EChatType type) cmdline_tp2name(name); } return false; + }else if(command == utf8str_tolower(gSavedSettings.getString("SinguCmdLineAway"))) + { + handle_fake_away_status(NULL); + return false; }else if(command == "typingstop") { std::string text; diff --git a/indra/newview/fmodwrapper.cpp b/indra/newview/fmodwrapper.cpp index d14c9134f..b544785b7 100644 --- a/indra/newview/fmodwrapper.cpp +++ b/indra/newview/fmodwrapper.cpp @@ -30,26 +30,30 @@ * $/LicenseInfo$ */ +//Hack to build Darwin. +//Need to find a better way to do this later. +#define LL_FMOD + extern "C" { -#if LL_FMODEX +#ifdef LL_FMODEX void FSOUND_Sound_Init(void); #endif -#if LL_FMOD + +#ifdef LL_FMOD void FSOUND_Init(void); #endif } void* fmodwrapper(void) -{ +{ // When building the fmodwrapper library, the linker doesn't seem to want to bring in libfmod.a unless I explicitly // reference at least one symbol in the library. This seemed like the simplest way. - void *ret = NULL; -#if LL_FMODEX - ret = (void*)&FSOUND_Sound_Init; +#ifdef LL_FMODEX + return (void*)&FSOUND_Sound_Init; #endif -#if LL_FMOD - ret = (void*)&FSOUND_Init; + +#ifdef LL_FMOD + return (void*)&FSOUND_Init; #endif - return ret; } diff --git a/indra/newview/hippogridmanager.cpp b/indra/newview/hippogridmanager.cpp index 85ee8a369..73dd8d124 100644 --- a/indra/newview/hippogridmanager.cpp +++ b/indra/newview/hippogridmanager.cpp @@ -40,8 +40,9 @@ HippoGridInfo HippoGridInfo::FALLBACK_GRIDINFO(""); // Initialize HippoGridInfo::HippoGridInfo(const std::string& gridName) : - mPlatform(PLATFORM_AURORA), + mPlatform(PLATFORM_OPENSIM), mGridName(gridName), + mGridNick(LLStringUtil::null), mLoginUri(LLStringUtil::null), mLoginPage(LLStringUtil::null), mHelperUri(LLStringUtil::null), @@ -50,14 +51,12 @@ HippoGridInfo::HippoGridInfo(const std::string& gridName) : mRegisterUrl(LLStringUtil::null), mPasswordUrl(LLStringUtil::null), mSearchUrl(LLStringUtil::null), - mFirstName(LLStringUtil::null), - mLastName(LLStringUtil::null), - mAvatarPassword(LLStringUtil::null), mGridMessage(""), mXmlState(XML_VOID), mVoiceConnector("SLVoice"), mRenderCompat(true), mInvLinks(false), + mAutoUpdate(false), mMaxAgentGroups(-1), mCurrencySymbol("OS$"), mRealCurrencySymbol("US$"), @@ -143,21 +142,6 @@ const std::string& HippoGridInfo::getGridMessage() const return mGridMessage; } -const std::string& HippoGridInfo::getFirstName() const -{ - return mFirstName; -} - -const std::string& HippoGridInfo::getLastName() const -{ - return mLastName; -} - -const std::string& HippoGridInfo::getAvatarPassword() const -{ - return mAvatarPassword; -} - bool HippoGridInfo::isRenderCompat() const { return mRenderCompat; @@ -215,7 +199,30 @@ void HippoGridInfo::setPlatform(const std::string& platform) void HippoGridInfo::setGridName(const std::string& gridName) { + HippoGridManager::GridIterator it; + for(it = gHippoGridManager->beginGrid(); it != gHippoGridManager->endGrid(); ++it) + { + if (it->second == this) + { + gHippoGridManager->mGridInfo.erase(it); + gHippoGridManager->mGridInfo[gridName] = this; + break; + } + } mGridName = gridName; + /*if(mGridNick.empty() && !gridName.empty()) + { + setGridNick(gridName); + }*/ +} + +void HippoGridInfo::setGridNick(std::string gridNick) +{ + mGridNick = sanitizeGridNick(gridNick); + if(mGridName.empty() && !gridNick.empty()) + { + setGridName(gridNick); + } } void HippoGridInfo::setLoginUri(const std::string& loginUri) @@ -265,21 +272,6 @@ void HippoGridInfo::setGridMessage(const std::string& message) mGridMessage = message; } -void HippoGridInfo::setFirstName(const std::string& firstName) -{ - mFirstName = firstName; -} - -void HippoGridInfo::setLastName(const std::string& lastName) -{ - mLastName = lastName; -} - -void HippoGridInfo::setAvatarPassword(const std::string& avatarPassword) -{ - mAvatarPassword = avatarPassword; -} - void HippoGridInfo::setRenderCompat(bool compat) { mRenderCompat = compat; @@ -376,25 +368,25 @@ std::string HippoGridInfo::getSearchUrl(SearchType ty, bool is_web) const } else { - // Use the old search all - if (ty == SEARCH_ALL_EMPTY) + // Use the old search all + if (ty == SEARCH_ALL_EMPTY) { - return (mSearchUrl + "panel=All&"); - } + return (mSearchUrl + "panel=All&"); + } else if (ty == SEARCH_ALL_QUERY) { - return (mSearchUrl + "q=[QUERY]&s=[COLLECTION]&"); - } + return (mSearchUrl + "q=[QUERY]&s=[COLLECTION]&"); + } else if (ty == SEARCH_ALL_TEMPLATE) { - return "lang=[LANG]&m=[MATURITY]&t=[TEEN]®ion=[REGION]&x=[X]&y=[Y]&z=[Z]&session=[SESSION]"; - } + return "lang=[LANG]&m=[MATURITY]&t=[TEEN]®ion=[REGION]&x=[X]&y=[Y]&z=[Z]&session=[SESSION]"; + } else { - llinfos << "Illegal search URL type " << ty << llendl; - return ""; - } - } + llinfos << "Illegal search URL type " << ty << llendl; + return ""; + } + } } @@ -402,7 +394,9 @@ std::string HippoGridInfo::getSearchUrl(SearchType ty, bool is_web) const void HippoGridInfo::onXmlElementStart(void* userData, const XML_Char* name, const XML_Char** atts) { HippoGridInfo* self = (HippoGridInfo*)userData; - if (strcasecmp(name, "gridname") == 0) + if (strcasecmp(name, "gridnick") == 0) + self->mXmlState = XML_GRIDNICK; + else if (strcasecmp(name, "gridname") == 0) self->mXmlState = XML_GRIDNAME; else if (strcasecmp(name, "platform") == 0) self->mXmlState = XML_PLATFORM; @@ -439,28 +433,35 @@ void HippoGridInfo::onXmlCharacterData(void* userData, const XML_Char* s, int le HippoGridInfo* self = (HippoGridInfo*)userData; switch (self->mXmlState) { - case XML_PLATFORM: + case XML_GRIDNICK: + { + if (self->mGridNick == "") self->mGridNick.assign(s, len); + self->mGridNick = sanitizeGridNick(self->mGridNick); + break; + } + + case XML_PLATFORM: { std::string platform(s, len); self->setPlatform(platform); break; } - case XML_LOGINURI: + case XML_LOGINURI: { std::string loginuri(s, len); self->mLoginUri = sanitizeUri( loginuri ); break; } - case XML_HELPERURI: + case XML_HELPERURI: { std::string helperuri(s, len); self->mHelperUri = sanitizeUri( helperuri ); break; } - case XML_SEARCH: + case XML_SEARCH: { self->mSearchUrl.assign(s, len); //sanitizeQueryUrl(mSearchUrl); @@ -547,6 +548,41 @@ void HippoGridInfo::formatFee(std::string &fee, int cost, bool showFree) const } } +//static +std::string HippoGridInfo::sanitizeGridNick(std::string &gridnick) +{ + std::string tmp; + int size = gridnick.size(); + for (int i=0; i 16) { + tmp.resize(16); + } + return tmp; +} + + +std::string HippoGridInfo::getGridNick() +{ + if(!mGridNick.empty()) + { + return mGridNick; + } + else + { + return sanitizeGridNick(mGridName); + } +} // ******************************************************************** // Static Helpers @@ -589,14 +625,16 @@ void HippoGridInfo::initFallback() FALLBACK_GRIDINFO.setHelperUri("http://127.0.0.1:9000/"); } -bool HippoGridInfo::supportsInvLinks(){ +bool HippoGridInfo::supportsInvLinks() +{ if(isSecondLife()) return true; else return mInvLinks; } -void HippoGridInfo::setSupportsInvLinks(bool b) { +void HippoGridInfo::setSupportsInvLinks(bool b) +{ if (b == true && mInvLinks == false) { llinfos << "Inventory Link support detected" << llendl; @@ -604,6 +642,19 @@ void HippoGridInfo::setSupportsInvLinks(bool b) { mInvLinks = b; } +bool HippoGridInfo::getAutoUpdate() +{ + if(isSecondLife()) + return false; + else + return mAutoUpdate; +} + +void HippoGridInfo::setAutoUpdate(bool b) +{ + mAutoUpdate = b; +} + // ******************************************************************** // ******************************************************************** // HippoGridManager @@ -615,8 +666,8 @@ void HippoGridInfo::setSupportsInvLinks(bool b) { // Initialize HippoGridManager::HippoGridManager() : - mConnectedGrid(0), - mDefaultGridsVersion(0), + mConnectedGrid(0), + mDefaultGridsVersion(0), mCurrentGrid("Local Host"), mDefaultGrid("Local Host") { @@ -759,13 +810,13 @@ void HippoGridManager::setDefaultGrid(const std::string& grid) { mDefaultGrid = grid; } - else if (mGridInfo.find("secondlife") != mGridInfo.end()) + else if (mGridInfo.find("Second life") != mGridInfo.end()) { - mDefaultGrid = "secondlife"; + mDefaultGrid = "Second Life"; } else if (!mGridInfo.empty()) { - mDefaultGrid = mGridInfo.begin()->first; + mDefaultGrid = mGridInfo.begin()->first; } else { @@ -784,7 +835,7 @@ void HippoGridManager::setCurrentGrid(const std::string& grid) else if (!mGridInfo.empty()) { llwarns << "Unknown grid '" << grid << "'. Setting to default grid." << llendl; - mCurrentGrid = mDefaultGrid; + mCurrentGrid = mDefaultGrid; } } @@ -796,7 +847,7 @@ void HippoGridManager::loadFromFile() { mDefaultGridsVersion = 0; // load user grid info - parseFile(gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "grids_sg2.xml"), false); + parseFile(gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "grids_sg1.xml"), false); // merge default grid info, if newer. Force load, if list of grids is empty. parseFile(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "default_grids.xml"), !mGridInfo.empty()); // merge grid info from web site, if newer. Force load, if list of grids is empty. @@ -886,14 +937,32 @@ void HippoGridManager::parseData(LLSD &gridInfo, bool mergeIfNewer) { mDefaultGridsVersion = gridMap["default_grids_version"]; } - else if (gridMap.has("gridname") && gridMap.has("loginuri")) + else if ((gridMap.has("gridnick") || gridMap.has("gridname")) && gridMap.has("loginuri")) { + std::string gridnick = gridMap["gridnick"]; std::string gridname = gridMap["gridname"]; + HippoGridInfo* grid; - GridIterator it = mGridInfo.find(gridname); - bool newGrid = (it == mGridInfo.end()); - if (newGrid) + GridIterator it = mGridInfo.end(); + for (it = mGridInfo.begin(); it != mGridInfo.end(); ++it) { + if(!gridnick.empty() && (it->second->getGridNick() == gridnick)) + { + break; + } + if(gridnick.empty() && !gridname.empty() && (it->first == gridname)) + { + break; + } + } + + bool newGrid = (it == mGridInfo.end()); + if (newGrid || !it->second) + { + if(gridname.empty()) + { + gridname = gridnick; + } // create new grid info grid = new HippoGridInfo(gridname); } @@ -905,6 +974,7 @@ void HippoGridManager::parseData(LLSD &gridInfo, bool mergeIfNewer) grid->setLoginUri(gridMap["loginuri"]); if (gridMap.has("platform")) grid->setPlatform(gridMap["platform"]); if (gridMap.has("gridname")) grid->setGridName(gridMap["gridname"]); + if (gridMap.has("gridnick")) grid->setGridNick(gridMap["gridnick"]); if (gridMap.has("loginpage")) grid->setLoginPage(gridMap["loginpage"]); if (gridMap.has("helperuri")) grid->setHelperUri(gridMap["helperuri"]); if (gridMap.has("website")) grid->setWebSite(gridMap["website"]); @@ -913,10 +983,8 @@ void HippoGridManager::parseData(LLSD &gridInfo, bool mergeIfNewer) if (gridMap.has("password")) grid->setPasswordUrl(gridMap["password"]); if (gridMap.has("search")) grid->setSearchUrl(gridMap["search"]); if (gridMap.has("render_compat")) grid->setRenderCompat(gridMap["render_compat"]); - if (gridMap.has("inventory_links")) grid->setSupportsInvLinks(gridMap["inventory_links"]); - // if (gridMap.has("firstname")) grid->setFirstName(gridMap["firstname"]); - // if (gridMap.has("lastname")) grid->setLastName(gridMap["lastname"]); - // if (gridMap.has("avatarpassword")) grid->setAvatarPassword(gridMap["avatarpassword"]); + if (gridMap.has("inventory_links")) grid->setSupportsInvLinks(gridMap["inventory_links"]); + if (gridMap.has("auto_update")) grid->setAutoUpdate(gridMap["auto_update"]); if (newGrid) addGrid(grid); } } @@ -939,6 +1007,7 @@ void HippoGridManager::saveFile() { HippoGridInfo* grid = it->second; gridInfo[i]["platform"] = HippoGridInfo::getPlatformString(grid->getPlatform()); + gridInfo[i]["gridnick"] = grid->getGridNick(); gridInfo[i]["gridname"] = grid->getGridName(); gridInfo[i]["loginuri"] = grid->getLoginUri(); gridInfo[i]["loginpage"] = grid->getLoginPage(); @@ -947,17 +1016,15 @@ void HippoGridManager::saveFile() gridInfo[i]["support"] = grid->getSupportUrl(); gridInfo[i]["register"] = grid->getRegisterUrl(); gridInfo[i]["password"] = grid->getPasswordUrl(); - // gridInfo[i]["firstname"] = grid->getFirstName(); - // gridInfo[i]["lastname"] = grid->getLastName(); - // gridInfo[i]["avatarpassword"] = grid->getAvatarPassword(); gridInfo[i]["search"] = grid->getSearchUrl(); gridInfo[i]["render_compat"] = grid->isRenderCompat(); gridInfo[i]["inventory_links"] = grid->supportsInvLinks(); + gridInfo[i]["auto_update"] = grid->getAutoUpdate(); } // write client grid info file - std::string fileName = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "grids_sg2.xml"); + std::string fileName = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "grids_sg1.xml"); llofstream file; file.open(fileName.c_str()); if (file.is_open()) diff --git a/indra/newview/hippogridmanager.h b/indra/newview/hippogridmanager.h index a5b26415d..4f3ef634e 100644 --- a/indra/newview/hippogridmanager.h +++ b/indra/newview/hippogridmanager.h @@ -51,12 +51,10 @@ public: // Returns the url base used for the Web Search tab const std::string& getSearchUrl() const; const std::string& getGridMessage() const; - const std::string& getFirstName() const; - const std::string& getLastName() const; - const std::string& getAvatarPassword() const; const std::string& getVoiceConnector() const { return mVoiceConnector; } std::string getSearchUrl(SearchType ty, bool is_web) const; bool isRenderCompat() const; + std::string getGridNick(); int getMaxAgentGroups() const { return mMaxAgentGroups; } const std::string& getCurrencySymbol() const; @@ -67,6 +65,7 @@ public: void setPlatform (const std::string& platform); void setPlatform (Platform platform); + void setGridNick (std::string gridNick); void setGridName (const std::string& gridName); void setLoginUri (const std::string& loginUri); void setLoginPage(const std::string& loginPage); @@ -80,9 +79,6 @@ public: void setGridMessage(const std::string& message); void setRenderCompat(bool compat); void setMaxAgentGroups(int max) { mMaxAgentGroups = max; } - void setFirstName(const std::string& firstName); - void setLastName(const std::string& lastName); - void setAvatarPassword(const std::string& avatarPassword); void setVoiceConnector(const std::string& vc) { mVoiceConnector = vc; } void setCurrencySymbol(const std::string& sym); @@ -90,10 +86,13 @@ public: void setDirectoryFee(int fee); bool supportsInvLinks(); void setSupportsInvLinks(bool b); + bool getAutoUpdate(); + void setAutoUpdate(bool b); bool retrieveGridInfo(); static const char* getPlatformString(Platform platform); + static std::string sanitizeGridNick(std::string &gridnick); static HippoGridInfo FALLBACK_GRIDINFO; static void initFallback(); @@ -101,6 +100,7 @@ public: private: Platform mPlatform; std::string mGridName; + std::string mGridNick; std::string mLoginUri; std::string mLoginPage; std::string mHelperUri; @@ -110,11 +110,9 @@ private: std::string mPasswordUrl; std::string mSearchUrl; std::string mVoiceConnector; - std::string mFirstName; - std::string mLastName; - std::string mAvatarPassword; bool mRenderCompat; bool mInvLinks; + bool mAutoUpdate; int mMaxAgentGroups; std::string mCurrencySymbol; @@ -125,7 +123,7 @@ private: // for parsing grid info XML enum XmlState { - XML_VOID, XML_PLATFORM, XML_GRIDNAME, + XML_VOID, XML_PLATFORM, XML_GRIDNAME, XML_GRIDNICK, XML_LOGINURI, XML_LOGINPAGE, XML_HELPERURI, XML_WEBSITE, XML_SUPPORT, XML_REGISTER, XML_PASSWORD, XML_SEARCH, XML_MESSAGE }; @@ -168,6 +166,7 @@ public: GridIterator endGrid() { return mGridInfo.end(); } private: + friend class HippoGridInfo; std::map mGridInfo; std::string mDefaultGrid; std::string mCurrentGrid; diff --git a/indra/newview/hippopanelgrids.cpp b/indra/newview/hippopanelgrids.cpp index d3cc7a4e2..6af943c5b 100644 --- a/indra/newview/hippopanelgrids.cpp +++ b/indra/newview/hippopanelgrids.cpp @@ -295,14 +295,21 @@ bool HippoPanelGridsImpl::saveCurGrid() HippoGridInfo *gridInfo = 0; gridInfo = gHippoGridManager->getGrid(mCurGrid); - gridInfo->retrieveGridInfo(); + //gridInfo->retrieveGridInfo(); refresh(); - + + std::string gridname = childGetValue("gridname"); + if (gridname == "") gridname = ""; + std::string loginuri = childGetValue("loginuri"); + if (loginuri == "") loginuri = ""; + + if (gridname.empty() && !loginuri.empty()) + this->retrieveGridInfo(); + if ((mState == ADD_NEW) || (mState == ADD_COPY)) { // check nickname std::string gridname = childGetValue("gridname"); - if (gridname == "") gridname = ""; childSetValue("gridname", (gridname != "")? gridname: ""); if (gridname == "") { LLNotificationsUtil::add("GridsNoNick"); @@ -316,8 +323,7 @@ bool HippoPanelGridsImpl::saveCurGrid() } // check login URI - std::string loginuri = childGetValue("loginuri"); - if ((loginuri == "") || (loginuri == "")) { + if (loginuri == "") { LLSD args; args["NAME"] = gridname; LLNotificationsUtil::add("GridsNoLoginUri", args); @@ -329,11 +335,6 @@ bool HippoPanelGridsImpl::saveCurGrid() gridInfo = new HippoGridInfo(gridname); gHippoGridManager->addGrid(gridInfo); gridInfo->retrieveGridInfo(); - } else { - - llwarns << "Illegal state " << mState << '.' << llendl; - return true; - } if (!gridInfo) { diff --git a/indra/newview/linux_tools/wrapper.sh b/indra/newview/linux_tools/wrapper.sh index 64f14a7c8..a20e94283 100755 --- a/indra/newview/linux_tools/wrapper.sh +++ b/indra/newview/linux_tools/wrapper.sh @@ -57,6 +57,11 @@ #export LL_WRAPPER='gdb --args' #export LL_WRAPPER='valgrind --smc-check=all --error-limit=no --log-file=secondlife.vg --leak-check=full --suppressions=/usr/lib/valgrind/glibc-2.5.supp --suppressions=secondlife-i686.supp' +## - This allows one to set an arbitrary value for LD_PRELOAD. +## It won't work if LL_TCMALLOC is set because that uses it's +## own value of LD_PRELOAD. +#export AI_PRELOAD='/path/to/libmemleak.so' + ## - Avoids an often-buggy X feature that doesn't really benefit us anyway. export SDL_VIDEO_X11_DGAMOUSE=0 @@ -96,6 +101,12 @@ cd "${RUN_PATH}" ## subprocesses that care. export SAVED_LD_LIBRARY_PATH="${LD_LIBRARY_PATH}" +SL_ENV= + +if [ -n "$AI_PRELOAD" ]; then + SL_ENV='LD_PRELOAD="$AI_PRELOAD" ' +fi + if [ -n "$LL_TCMALLOC" ]; then tcmalloc_libs='/usr/lib/libtcmalloc.so.0 /usr/lib/libstacktrace.so.0 /lib/libpthread.so.0' all=1 @@ -107,7 +118,7 @@ if [ -n "$LL_TCMALLOC" ]; then if [ $all != 1 ]; then echo 'Cannot use tcmalloc libraries: components missing' 1>&2 else - export LD_PRELOAD=$(echo $tcmalloc_libs | tr ' ' :) + SL_ENV='LD_PRELOAD="$(echo $tcmalloc_libs | tr '"' '"' :)" ' if [ -z "$HEAPCHECK" -a -z "$HEAPPROFILE" ]; then export HEAPCHECK=${HEAPCHECK:-normal} fi @@ -119,10 +130,10 @@ BINARY_TYPE=$(expr match "$(file -b bin/$VIEWER_BINARY)" '\(.*executable\)') QPP=qt4/plugins/imageformats/ if [ "${BINARY_TYPE}" == "ELF 64-bit LSB executable" ]; then QTPLUGINS=/usr/lib64/$QPP:/lib64/$QPP:/usr/local/lib64/$QPP - export SL_ENV='LD_LIBRARY_PATH="`pwd`"/lib64:"`pwd`"/lib32:$QTPLUGINS:"${LD_LIBRARY_PATH}"' + SL_ENV+='LD_LIBRARY_PATH="`pwd`/lib64:`pwd`/lib32:$QTPLUGINS:$LD_LIBRARY_PATH"' else QTPLUGINS=/usr/lib/$QPP:/lib/$QPP:/usr/local/lib/$QPP - export SL_ENV='LD_LIBRARY_PATH="`pwd`"/lib:$QTPLUGINS:"${LD_LIBRARY_PATH}"' + SL_ENV+='LD_LIBRARY_PATH="`pwd`/lib:$QTPLUGINS:$LD_LIBRARY_PATH"' fi export SL_CMD='$LL_WRAPPER bin/$VIEWER_BINARY' diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index f1dac09f8..f2150ae2d 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -57,6 +57,7 @@ #include "llnotificationsutil.h" #include "llparcel.h" #include "llrendersphere.h" +#include "llsdmessage.h" #include "llsdutil.h" #include "llsky.h" #include "llsmoothstep.h" @@ -2105,11 +2106,15 @@ LLVector3 ll_vector3_from_sdmap(const LLSD& sd) void LLAgent::setStartPosition( U32 location_id ) { + LLViewerObject *object; + if (gAgentID == LLUUID::null) { return; } - if (gObjectList.findAvatar(gAgentID) == NULL) + // we've got an ID for an agent viewerobject + object = gObjectList.findObject(gAgentID); + if (! object) { llinfos << "setStartPosition - Can't find agent viewerobject id " << gAgentID << llendl; return; @@ -2126,7 +2131,7 @@ void LLAgent::setStartPosition( U32 location_id ) if (isAgentAvatarValid()) { // the z height is at the agent's feet - agent_pos.mV[VZ] -= 0.5f * gAgentAvatarp->mBodySize.mV[VZ]; + agent_pos.mV[VZ] -= 0.5f * gAgentAvatarp->mBodySize.mV[VZ]; } agent_pos.mV[VX] = llclamp( agent_pos.mV[VX], INSET, REGION_WIDTH - INSET ); @@ -2136,46 +2141,44 @@ void LLAgent::setStartPosition( U32 location_id ) agent_pos.mV[VZ] = llclamp( agent_pos.mV[VZ], mRegionp->getLandHeightRegion( agent_pos ), LLWorld::getInstance()->getRegionMaxHeight() ); - std::string url = gAgent.getRegion()->getCapability("HomeLocation"); - if( !url.empty() ) - { - // Send the CapReq - LLSD request; - LLSD body; - LLSD homeLocation; + // Send the CapReq + LLSD request; + LLSD body; + LLSD homeLocation; - homeLocation["LocationId"] = LLSD::Integer(location_id); - homeLocation["LocationPos"] = ll_sdmap_from_vector3(agent_pos); - homeLocation["LocationLookAt"] = ll_sdmap_from_vector3(mFrameAgent.getAtAxis()); + homeLocation["LocationId"] = LLSD::Integer(location_id); + homeLocation["LocationPos"] = ll_sdmap_from_vector3(agent_pos); + homeLocation["LocationLookAt"] = ll_sdmap_from_vector3(mFrameAgent.getAtAxis()); - body["HomeLocation"] = homeLocation; + body["HomeLocation"] = homeLocation; - LLHTTPClient::post( url, body, new LLHomeLocationResponder() ); - } - else - { - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_SetStartLocationRequest); - msg->nextBlockFast( _PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, getID()); - msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); - msg->nextBlockFast( _PREHASH_StartLocationData); - // corrected by sim - msg->addStringFast(_PREHASH_SimName, ""); - msg->addU32Fast(_PREHASH_LocationID, location_id); - msg->addVector3Fast(_PREHASH_LocationPos, agent_pos); - msg->addVector3Fast(_PREHASH_LocationLookAt,mFrameAgent.getAtAxis()); - - // Reliable only helps when setting home location. Last - // location is sent on quit, and we don't have time to ack - // the packets. - msg->sendReliable(mRegionp->getHost()); - } - const U32 HOME_INDEX = 1; - if( HOME_INDEX == location_id ) - { - setHomePosRegion( mRegionp->getHandle(), getPositionAgent() ); - } + // This awkward idiom warrants explanation. + // For starters, LLSDMessage::ResponderAdapter is ONLY for testing the new + // LLSDMessage functionality with a pre-existing LLHTTPClient::Responder. + // In new code, define your reply/error methods on the same class as the + // sending method, bind them to local LLEventPump objects and pass those + // LLEventPump names in the request LLSD object. + // When testing old code, the new LLHomeLocationResponder object + // is referenced by an LLHTTPClient::ResponderPtr, so when the + // ResponderAdapter is deleted, the LLHomeLocationResponder will be too. + // We must trust that the underlying LLHTTPClient code will eventually + // fire either the reply callback or the error callback; either will cause + // the ResponderAdapter to delete itself. + LLSDMessage::ResponderAdapter* + adapter(new LLSDMessage::ResponderAdapter(new LLHomeLocationResponder())); + + request["message"] = "HomeLocation"; + request["payload"] = body; + request["reply"] = adapter->getReplyName(); + request["error"] = adapter->getErrorName(); + + gAgent.getRegion()->getCapAPI().post(request); + + const U32 HOME_INDEX = 1; + if( HOME_INDEX == location_id ) + { + setHomePosRegion( mRegionp->getHandle(), getPositionAgent() ); + } } void LLAgent::requestStopMotion( LLMotion* motion ) @@ -2818,7 +2821,7 @@ void update_group_floaters(const LLUUID& group_id) gIMMgr->refresh(); } - gAgent.fireEvent(new LLEvent(&gAgent, "new group"), ""); + gAgent.fireEvent(new LLOldEvents::LLEvent(&gAgent, "new group"), ""); } // static @@ -3982,7 +3985,10 @@ void LLAgent::sendAgentSetAppearance() } } -// llinfos << "Avatar XML num VisualParams transmitted = " << transmitted_params << llendl; + llinfos << "Avatar XML num VisualParams transmitted = " << transmitted_params << llendl; + if(transmitted_params < 218) { + LLNotificationsUtil::add("SGIncompleteAppearence"); + } sendReliableMessage(); } diff --git a/indra/newview/llagent.h b/indra/newview/llagent.h index 846edf8d4..145644f7b 100644 --- a/indra/newview/llagent.h +++ b/indra/newview/llagent.h @@ -92,7 +92,7 @@ struct LLGroupData // -class LLAgent : public LLObservable +class LLAgent : public LLOldEvents::LLObservable { LOG_CLASS(LLAgent); diff --git a/indra/newview/llagentlanguage.h b/indra/newview/llagentlanguage.h index 6bc7250c6..596c58423 100644 --- a/indra/newview/llagentlanguage.h +++ b/indra/newview/llagentlanguage.h @@ -36,7 +36,7 @@ #include "llmemory.h" // LLSingleton<> #include "llevent.h" -class LLAgentLanguage: public LLSingleton, public LLSimpleListener +class LLAgentLanguage: public LLSingleton, public LLOldEvents::LLSimpleListener { public: LLAgentLanguage(); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index e8281457f..3c4cbd97f 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -37,7 +37,7 @@ #include "hippogridmanager.h" #include "hippolimits.h" -#include "llversionviewer.h" +#include "sgversion.h" #include "llfeaturemanager.h" #include "lluictrlfactory.h" #include "lltexteditor.h" @@ -643,11 +643,11 @@ bool LLAppViewer::init() // Build a string representing the current version number. gCurrentVersion = llformat("%s %d.%d.%d.%d", - LL_CHANNEL, - LL_VERSION_MAJOR, - LL_VERSION_MINOR, - LL_VERSION_PATCH, - LL_VERSION_BUILD ); + gVersionChannel, + gVersionMajor, + gVersionMinor, + gVersionPatch, + gVersionBuild ); ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// @@ -2045,7 +2045,7 @@ bool LLAppViewer::initConfiguration() gSavedSettings.setString("ClientSettingsFile", gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, getSettingsFilename("Default", "Global"))); - gSavedSettings.setString("VersionChannelName", LL_CHANNEL); + gSavedSettings.setString("VersionChannelName", gVersionChannel); #ifndef LL_RELEASE_FOR_DOWNLOAD // provide developer build only overrides for these control variables that are not @@ -2654,12 +2654,12 @@ void LLAppViewer::writeSystemInfo() { gDebugInfo["SLLog"] = LLError::logFileName(); - gDebugInfo["ClientInfo"]["Name"] = LL_CHANNEL; + gDebugInfo["ClientInfo"]["Name"] = gVersionChannel; - gDebugInfo["ClientInfo"]["MajorVersion"] = LL_VERSION_MAJOR; - gDebugInfo["ClientInfo"]["MinorVersion"] = LL_VERSION_MINOR; - gDebugInfo["ClientInfo"]["PatchVersion"] = LL_VERSION_PATCH; - gDebugInfo["ClientInfo"]["BuildVersion"] = LL_VERSION_BUILD; + gDebugInfo["ClientInfo"]["MajorVersion"] = gVersionMajor; + gDebugInfo["ClientInfo"]["MinorVersion"] = gVersionMinor; + gDebugInfo["ClientInfo"]["PatchVersion"] = gVersionPatch; + gDebugInfo["ClientInfo"]["BuildVersion"] = gVersionBuild; gDebugInfo["CAFilename"] = gDirUtilp->getCAFile(); @@ -2694,7 +2694,7 @@ void LLAppViewer::writeSystemInfo() // Dump some debugging info LL_INFOS("SystemInfo") << LLTrans::getString("APP_NAME") - << " version " << LL_VERSION_MAJOR << "." << LL_VERSION_MINOR << "." << LL_VERSION_PATCH + << " version " << gVersionMajor << "." << gVersionMinor << "." << gVersionPatch << LL_ENDL; // Dump the local time and time zone @@ -2758,12 +2758,12 @@ void LLAppViewer::handleViewerCrash() //We already do this in writeSystemInfo(), but we do it again here to make /sure/ we have a version //to check against no matter what - gDebugInfo["ClientInfo"]["Name"] = LL_CHANNEL; + gDebugInfo["ClientInfo"]["Name"] = gVersionChannel; - gDebugInfo["ClientInfo"]["MajorVersion"] = LL_VERSION_MAJOR; - gDebugInfo["ClientInfo"]["MinorVersion"] = LL_VERSION_MINOR; - gDebugInfo["ClientInfo"]["PatchVersion"] = LL_VERSION_PATCH; - gDebugInfo["ClientInfo"]["BuildVersion"] = LL_VERSION_BUILD; + gDebugInfo["ClientInfo"]["MajorVersion"] = gVersionMajor; + gDebugInfo["ClientInfo"]["MinorVersion"] = gVersionMinor; + gDebugInfo["ClientInfo"]["PatchVersion"] = gVersionPatch; + gDebugInfo["ClientInfo"]["BuildVersion"] = gVersionBuild; LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); if ( parcel && parcel->getMusicURL()[0]) @@ -4607,12 +4607,12 @@ void LLAppViewer::handleLoginComplete() initMainloopTimeout("Mainloop Init"); // Store some data to DebugInfo in case of a freeze. - gDebugInfo["ClientInfo"]["Name"] = LL_CHANNEL; + gDebugInfo["ClientInfo"]["Name"] = gVersionChannel; - gDebugInfo["ClientInfo"]["MajorVersion"] = LL_VERSION_MAJOR; - gDebugInfo["ClientInfo"]["MinorVersion"] = LL_VERSION_MINOR; - gDebugInfo["ClientInfo"]["PatchVersion"] = LL_VERSION_PATCH; - gDebugInfo["ClientInfo"]["BuildVersion"] = LL_VERSION_BUILD; + gDebugInfo["ClientInfo"]["MajorVersion"] = gVersionMajor; + gDebugInfo["ClientInfo"]["MinorVersion"] = gVersionMinor; + gDebugInfo["ClientInfo"]["PatchVersion"] = gVersionPatch; + gDebugInfo["ClientInfo"]["BuildVersion"] = gVersionBuild; LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); if ( parcel && parcel->getMusicURL()[0]) diff --git a/indra/newview/llcapabilitylistener.cpp b/indra/newview/llcapabilitylistener.cpp new file mode 100644 index 000000000..ef9b910ae --- /dev/null +++ b/indra/newview/llcapabilitylistener.cpp @@ -0,0 +1,202 @@ +/** + * @file llcapabilitylistener.cpp + * @author Nat Goodspeed + * @date 2009-01-07 + * @brief Implementation for llcapabilitylistener. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Precompiled header +#include "llviewerprecompiledheaders.h" +// associated header +#include "llcapabilitylistener.h" +// STL headers +#include +// std headers +// external library headers +#include +// other Linden headers +#include "stringize.h" +#include "llcapabilityprovider.h" +#include "message.h" + +class LLCapabilityListener::CapabilityMappers: public LLSingleton +{ +public: + void registerMapper(const LLCapabilityListener::CapabilityMapper*); + void unregisterMapper(const LLCapabilityListener::CapabilityMapper*); + const LLCapabilityListener::CapabilityMapper* find(const std::string& cap) const; + + struct DupCapMapper: public std::runtime_error + { + DupCapMapper(const std::string& what): + std::runtime_error(std::string("DupCapMapper: ") + what) + {} + }; + +private: + friend class LLSingleton; + CapabilityMappers(); + + typedef std::map CapabilityMap; + CapabilityMap mMap; +}; + +LLCapabilityListener::LLCapabilityListener(const std::string& name, + LLMessageSystem* messageSystem, + const LLCapabilityProvider& provider, + const LLUUID& agentID, + const LLUUID& sessionID): + mEventPump(name), + mMessageSystem(messageSystem), + mProvider(provider), + mAgentID(agentID), + mSessionID(sessionID) +{ + mEventPump.listen("self", boost::bind(&LLCapabilityListener::capListener, this, _1)); +} + +bool LLCapabilityListener::capListener(const LLSD& request) +{ + // Extract what we want from the request object. We do it all up front + // partly to document what we expect. + LLSD::String cap(request["message"]); + LLSD payload(request["payload"]); + LLSD::String reply(request["reply"]); + LLSD::String error(request["error"]); + LLSD::Real timeout(request["timeout"]); + // If the LLSD doesn't even have a "message" key, we doubt it was intended + // for this listener. + if (cap.empty()) + { + LL_ERRS("capListener") << "capability request event without 'message' key to '" + << getCapAPI().getName() + << "' on region\n" << mProvider.getDescription() + << LL_ENDL; + return false; // in case fatal-error function isn't + } + // Establish default timeout. This test relies on LLSD::asReal() returning + // exactly 0.0 for an undef value. + if (! timeout) + { + timeout = HTTP_REQUEST_EXPIRY_SECS; + } + // Look up the url for the requested capability name. + std::string url = mProvider.getCapability(cap); + if (! url.empty()) + { + // This capability is supported by the region to which we're talking. + LLHTTPClient::post(url, payload, + new LLSDMessage::EventResponder(LLEventPumps::instance(), + request, + mProvider.getDescription(), + cap, reply, error), + LLSD(), // headers + timeout); + } + else + { + // Capability not supported -- do we have a registered mapper? + const CapabilityMapper* mapper = CapabilityMappers::instance().find(cap); + if (! mapper) // capability neither supported nor mapped + { + LL_ERRS("capListener") << "unsupported capability '" << cap << "' request to '" + << getCapAPI().getName() << "' on region\n" + << mProvider.getDescription() + << LL_ENDL; + } + else if (! mapper->getReplyName().empty()) // mapper expects reply support + { + LL_ERRS("capListener") << "Mapper for capability '" << cap + << "' requires unimplemented support for reply message '" + << mapper->getReplyName() + << "' on '" << getCapAPI().getName() << "' on region\n" + << mProvider.getDescription() + << LL_ENDL; + } + else + { + LL_INFOS("capListener") << "fallback invoked for capability '" << cap + << "' request to '" << getCapAPI().getName() + << "' on region\n" << mProvider.getDescription() + << LL_ENDL; + mapper->buildMessage(mMessageSystem, mAgentID, mSessionID, cap, payload); + mMessageSystem->sendReliable(mProvider.getHost()); + } + } + return false; +} + +LLCapabilityListener::CapabilityMapper::CapabilityMapper(const std::string& cap, const std::string& reply): + mCapName(cap), + mReplyName(reply) +{ + LLCapabilityListener::CapabilityMappers::instance().registerMapper(this); +} + +LLCapabilityListener::CapabilityMapper::~CapabilityMapper() +{ + LLCapabilityListener::CapabilityMappers::instance().unregisterMapper(this); +} + +LLSD LLCapabilityListener::CapabilityMapper::readResponse(LLMessageSystem* messageSystem) const +{ + return LLSD(); +} + +LLCapabilityListener::CapabilityMappers::CapabilityMappers() {} + +void LLCapabilityListener::CapabilityMappers::registerMapper(const LLCapabilityListener::CapabilityMapper* mapper) +{ + // Try to insert a new map entry by which we can look up the passed mapper + // instance. + std::pair inserted = + mMap.insert(CapabilityMap::value_type(mapper->getCapName(), mapper)); + // If we already have a mapper for that name, insert() merely located the + // existing iterator and returned false. It is a coding error to try to + // register more than one mapper for the same capability name. + if (! inserted.second) + { + throw DupCapMapper(std::string("Duplicate capability name ") + mapper->getCapName()); + } +} + +void LLCapabilityListener::CapabilityMappers::unregisterMapper(const LLCapabilityListener::CapabilityMapper* mapper) +{ + CapabilityMap::iterator found = mMap.find(mapper->getCapName()); + if (found != mMap.end()) + { + mMap.erase(found); + } +} + +const LLCapabilityListener::CapabilityMapper* +LLCapabilityListener::CapabilityMappers::find(const std::string& cap) const +{ + CapabilityMap::const_iterator found = mMap.find(cap); + if (found != mMap.end()) + { + return found->second; + } + return NULL; +} diff --git a/indra/newview/llcapabilitylistener.h b/indra/newview/llcapabilitylistener.h new file mode 100644 index 000000000..e7535013e --- /dev/null +++ b/indra/newview/llcapabilitylistener.h @@ -0,0 +1,131 @@ +/** + * @file llcapabilitylistener.h + * @author Nat Goodspeed + * @date 2009-01-07 + * @brief Provide an event-based API for capability requests + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, 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$ + */ + +#if ! defined(LL_LLCAPABILITYLISTENER_H) +#define LL_LLCAPABILITYLISTENER_H + +#include "llevents.h" // LLEventPump +#include "llsdmessage.h" // LLSDMessage::ArgError +#include "llerror.h" // LOG_CLASS() + +class LLCapabilityProvider; +class LLMessageSystem; +class LLSD; + +class LLCapabilityListener +{ + LOG_CLASS(LLCapabilityListener); +public: + LLCapabilityListener(const std::string& name, LLMessageSystem* messageSystem, + const LLCapabilityProvider& provider, + const LLUUID& agentID, const LLUUID& sessionID); + + /// Capability-request exception + typedef LLSDMessage::ArgError ArgError; + /// Get LLEventPump on which we listen for capability requests + /// (https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes#Capabilities) + LLEventPump& getCapAPI() { return mEventPump; } + + /** + * Base class for mapping an as-yet-undeployed capability name to a (pair + * of) LLMessageSystem message(s). To map a capability name to such + * messages, derive a subclass of CapabilityMapper and declare a static + * instance in a translation unit known to be loaded. The mapping is not + * region-specific. If an LLViewerRegion's capListener() receives a + * request for a supported capability, it will use the capability's URL. + * If not, it will look for an applicable CapabilityMapper subclass + * instance. + */ + class CapabilityMapper + { + public: + /** + * Base-class constructor. Typically your subclass constructor will + * pass these parameters as literals. + * @param cap the capability name handled by this (subclass) instance + * @param reply the name of the response LLMessageSystem message. Omit + * if the LLMessageSystem message you intend to send doesn't prompt a + * reply message, or if you already handle that message in some other + * way. + */ + CapabilityMapper(const std::string& cap, const std::string& reply = ""); + virtual ~CapabilityMapper(); + /// query the capability name + std::string getCapName() const { return mCapName; } + /// query the reply message name + std::string getReplyName() const { return mReplyName; } + /** + * Override this method to build the LLMessageSystem message we should + * send instead of the requested capability message. DO NOT send that + * message: that will be handled by the caller. + */ + virtual void buildMessage(LLMessageSystem* messageSystem, + const LLUUID& agentID, + const LLUUID& sessionID, + const std::string& capabilityName, + const LLSD& payload) const = 0; + /** + * Override this method if you pass a non-empty @a reply + * LLMessageSystem message name to the constructor: that is, if you + * expect to receive an LLMessageSystem message in response to the + * message you constructed in buildMessage(). If you don't pass a @a + * reply message name, you need not override this method as it won't + * be called. + * + * Using LLMessageSystem message-reading operations, your + * readResponse() override should construct and return an LLSD object + * of the form you expect to receive from the real implementation of + * the capability you intend to invoke, when it finally goes live. + */ + virtual LLSD readResponse(LLMessageSystem* messageSystem) const; + + private: + const std::string mCapName; + const std::string mReplyName; + }; + +private: + /// Bind the LLCapabilityProvider passed to our ctor + const LLCapabilityProvider& mProvider; + + /// Post an event to this LLEventPump to invoke a capability message on + /// the bound LLCapabilityProvider's server + /// (https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes#Capabilities) + LLEventStream mEventPump; + + LLMessageSystem* mMessageSystem; + LLUUID mAgentID, mSessionID; + + /// listener to process capability requests + bool capListener(const LLSD&); + + /// helper class for capListener() + class CapabilityMappers; +}; + +#endif /* ! defined(LL_LLCAPABILITYLISTENER_H) */ diff --git a/indra/newview/lldrawpool.cpp b/indra/newview/lldrawpool.cpp index 77f25bc6b..968ed740a 100644 --- a/indra/newview/lldrawpool.cpp +++ b/indra/newview/lldrawpool.cpp @@ -41,7 +41,6 @@ #include "lldrawpoolalpha.h" #include "lldrawpoolavatar.h" #include "lldrawpoolbump.h" -#include "lldrawpoolclouds.h" #include "lldrawpoolground.h" #include "lldrawpoolsimple.h" #include "lldrawpoolsky.h" diff --git a/indra/newview/lldrawpool.h b/indra/newview/lldrawpool.h index 225c93637..341583996 100644 --- a/indra/newview/lldrawpool.h +++ b/indra/newview/lldrawpool.h @@ -139,6 +139,7 @@ public: PASS_ALPHA, PASS_ALPHA_MASK, PASS_FULLBRIGHT_ALPHA_MASK, + PASS_ALPHA_INVISIBLE, NUM_RENDER_TYPES, }; diff --git a/indra/newview/lldrawpoolalpha.cpp b/indra/newview/lldrawpoolalpha.cpp index d138d4552..4d4864470 100644 --- a/indra/newview/lldrawpoolalpha.cpp +++ b/indra/newview/lldrawpoolalpha.cpp @@ -344,6 +344,7 @@ void LLDrawPoolAlpha::render(S32 pass) pushBatches(LLRenderPass::PASS_ALPHA_MASK, LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD0, FALSE); pushBatches(LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK, LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD0, FALSE); + pushBatches(LLRenderPass::PASS_ALPHA_INVISIBLE, LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD0, FALSE); if(shaders) { diff --git a/indra/newview/lldrawpoolbump.cpp b/indra/newview/lldrawpoolbump.cpp index ff47bed4e..a88c7ce46 100644 --- a/indra/newview/lldrawpoolbump.cpp +++ b/indra/newview/lldrawpoolbump.cpp @@ -1589,7 +1589,11 @@ void LLDrawPoolInvisible::endDeferredPass( S32 pass ) void LLDrawPoolInvisible::renderDeferred( S32 pass ) { //render invisiprims; this doesn't work becaue it also blocks all the post-deferred stuff -#if 0 + static const LLCachedControl enable("SianaRenderDeferredInvisiprim"); + if (!enable) { + return; + } + LLFastTimer t(FTM_RENDER_INVISIBLE); U32 invisi_mask = LLVertexBuffer::MAP_VERTEX; @@ -1607,5 +1611,4 @@ void LLDrawPoolInvisible::renderDeferred( S32 pass ) renderShiny(true); endShiny(true); } -#endif } diff --git a/indra/newview/lldrawpoolclouds.cpp b/indra/newview/lldrawpoolclouds.cpp deleted file mode 100644 index 0ded5695d..000000000 --- a/indra/newview/lldrawpoolclouds.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/** - * @file lldrawpoolclouds.cpp - * @brief LLDrawPoolClouds class implementation - * - * $LicenseInfo:firstyear=2006&license=viewergpl$ - * - * Copyright (c) 2006-2009, Linden Research, Inc. - * - * Second Life Viewer Source Code - * The source code in this file ("Source Code") is provided by Linden Lab - * to you under the terms of the GNU General Public License, version 2.0 - * ("GPL"), unless you have obtained a separate licensing agreement - * ("Other License"), formally executed by you and Linden Lab. Terms of - * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 - * - * There are special exceptions to the terms and conditions of the GPL as - * it is applied to this Source Code. View the full text of the exception - * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception - * - * By copying, modifying or distributing this software, you acknowledge - * that you have read and understood your obligations described above, - * and agree to abide by those obligations. - * - * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO - * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, - * COMPLETENESS OR PERFORMANCE. - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lldrawpoolclouds.h" - -#include "llface.h" -#include "llsky.h" -#include "llviewercamera.h" -#include "llvoclouds.h" -#include "pipeline.h" - -LLDrawPoolClouds::LLDrawPoolClouds() : - LLDrawPool(POOL_CLOUDS) -{ -} - -LLDrawPool *LLDrawPoolClouds::instancePool() -{ - return new LLDrawPoolClouds(); -} - -BOOL LLDrawPoolClouds::addFace(LLFace* face) -{ - llerrs << "WTF?" << llendl; - return FALSE; -} - -void LLDrawPoolClouds::enqueue(LLFace *facep) -{ - mDrawFace.push_back(facep); - facep->mDistance = (facep->mCenterAgent - gCamera->getOrigin()) * gCamera->getAtAxis(); -} - -void LLDrawPoolClouds::beginRenderPass(S32 pass) -{ - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glEnableClientState(GL_NORMAL_ARRAY); -} - -void LLDrawPoolClouds::prerender() -{ - mVertexShaderLevel = gPipeline.getVertexShaderLevel(LLPipeline::SHADER_ENVIRONMENT); -} - -void LLDrawPoolClouds::render(S32 pass) -{ - LLFastTimer ftm(FTM_RENDER_CLOUDS); - if (!(gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_CLOUDS))) - { - return; - } - - if (mDrawFace.empty()) - { - return; - } - - LLGLSPipelineAlpha gls_pipeline_alpha; - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); - glAlphaFunc(GL_GREATER,0.01f); - - gPipeline.enableLightsFullbright(LLColor4(1.f,1.f,1.f)); - - mDrawFace[0]->bindTexture(); - - std::sort(mDrawFace.begin(), mDrawFace.end(), LLFace::CompareDistanceGreater()); - - drawLoop(); -} - - diff --git a/indra/newview/lldrawpoolclouds.h b/indra/newview/lldrawpoolclouds.h deleted file mode 100644 index 7c95b3ec0..000000000 --- a/indra/newview/lldrawpoolclouds.h +++ /dev/null @@ -1,60 +0,0 @@ -/** - * @file lldrawpoolclouds.h - * @brief LLDrawPoolClouds class definition - * - * $LicenseInfo:firstyear=2006&license=viewergpl$ - * - * Copyright (c) 2006-2009, Linden Research, Inc. - * - * Second Life Viewer Source Code - * The source code in this file ("Source Code") is provided by Linden Lab - * to you under the terms of the GNU General Public License, version 2.0 - * ("GPL"), unless you have obtained a separate licensing agreement - * ("Other License"), formally executed by you and Linden Lab. Terms of - * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 - * - * There are special exceptions to the terms and conditions of the GPL as - * it is applied to this Source Code. View the full text of the exception - * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception - * - * By copying, modifying or distributing this software, you acknowledge - * that you have read and understood your obligations described above, - * and agree to abide by those obligations. - * - * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO - * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, - * COMPLETENESS OR PERFORMANCE. - * $/LicenseInfo$ - */ - -#ifndef LL_LLDRAWPOOLCLOUDS_H -#define LL_LLDRAWPOOLCLOUDS_H - -#include "lldrawpool.h" - -class LLDrawPoolClouds : public LLDrawPool -{ -public: - enum - { - VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | - LLVertexBuffer::MAP_NORMAL | - LLVertexBuffer::MAP_TEXCOORD0 - }; - - BOOL addFace(LLFace* face); - virtual U32 getVertexDataMask() { return VERTEX_DATA_MASK; } - - LLDrawPoolClouds(); - - /*virtual*/ void prerender(); - /*virtual*/ LLDrawPool *instancePool(); - /*virtual*/ void enqueue(LLFace *face); - /*virtual*/ void beginRenderPass(S32 pass); - /*virtual*/ void render(S32 pass = 0); -}; - -#endif // LL_LLDRAWPOOLSKY_H diff --git a/indra/newview/llfloaterabout.cpp b/indra/newview/llfloaterabout.cpp index 0bd2ba83d..8516035d3 100644 --- a/indra/newview/llfloaterabout.cpp +++ b/indra/newview/llfloaterabout.cpp @@ -49,7 +49,7 @@ #include "llagent.h" #include "llviewerstats.h" #include "llviewerregion.h" -#include "llversionviewer.h" +#include "sgversion.h" #include "llviewerbuild.h" #include "lluictrlfactory.h" #include "lluri.h" @@ -137,9 +137,9 @@ LLFloaterAbout::LLFloaterAbout() // Version string std::string version = std::string(LLAppViewer::instance()->getSecondLifeTitle() + llformat(" %d.%d.%d (%d) %s %s (%s)\n", - LL_VERSION_MAJOR, LL_VERSION_MINOR, LL_VERSION_PATCH, LL_VIEWER_BUILD, + gVersionMajor, gVersionMinor, gVersionPatch, LL_VIEWER_BUILD, __DATE__, __TIME__, - LL_CHANNEL)); + gVersionChannel)); support_widget->appendColoredText(version, FALSE, FALSE, gColors.getColor("TextFgReadOnlyColor")); support_widget->appendStyledText(LLTrans::getString("ReleaseNotes"), false, false, viewer_link_style); @@ -331,13 +331,13 @@ static std::string get_viewer_release_notes_url() { return "http://www.singularityviewer.org"; /*std::ostringstream version; - version << LL_VERSION_MAJOR - << "." << LL_VERSION_MINOR - << "." << LL_VERSION_PATCH - << "." << LL_VERSION_BUILD; + version << gVersionMajor + << "." << gVersionMinor + << "." << gVersionPatch + << "." << gVersionBuild; LLSD query; - query["channel"] = LL_CHANNEL; + query["channel"] = gVersionChannel; query["version"] = version.str(); diff --git a/indra/newview/llfloateractivespeakers.cpp b/indra/newview/llfloateractivespeakers.cpp index 282f9f842..6db1c30fa 100644 --- a/indra/newview/llfloateractivespeakers.cpp +++ b/indra/newview/llfloateractivespeakers.cpp @@ -57,6 +57,8 @@ #include "llavatarname.h" +using namespace LLOldEvents; + const F32 SPEAKER_TIMEOUT = 10.f; // seconds of not being on voice channel before removed from list of active speakers const F32 RESORT_TIMEOUT = 5.f; // seconds of mouse inactivity before it's ok to sort regardless of mouse-in-view. const LLColor4 INACTIVE_COLOR(0.3f, 0.3f, 0.3f, 0.5f); diff --git a/indra/newview/llfloateractivespeakers.h b/indra/newview/llfloateractivespeakers.h index d85f3c076..8a70e77a0 100644 --- a/indra/newview/llfloateractivespeakers.h +++ b/indra/newview/llfloateractivespeakers.h @@ -49,7 +49,7 @@ class LLVoiceChannel; // data for a given participant in a voice channel -class LLSpeaker : public LLRefCount, public LLObservable, public LLHandleProvider +class LLSpeaker : public LLRefCount, public LLOldEvents::LLObservable, public LLHandleProvider { public: typedef enum e_speaker_type @@ -96,21 +96,21 @@ public: std::string mLegacyName; }; -class LLSpeakerTextModerationEvent : public LLEvent +class LLSpeakerTextModerationEvent : public LLOldEvents::LLEvent { public: LLSpeakerTextModerationEvent(LLSpeaker* source); /*virtual*/ LLSD getValue(); }; -class LLSpeakerVoiceModerationEvent : public LLEvent +class LLSpeakerVoiceModerationEvent : public LLOldEvents::LLEvent { public: LLSpeakerVoiceModerationEvent(LLSpeaker* source); /*virtual*/ LLSD getValue(); }; -class LLSpeakerListChangeEvent : public LLEvent +class LLSpeakerListChangeEvent : public LLOldEvents::LLEvent { public: LLSpeakerListChangeEvent(LLSpeakerMgr* source, const LLUUID& speaker_id); @@ -120,7 +120,7 @@ private: const LLUUID& mSpeakerID; }; -class LLSpeakerMgr : public LLObservable +class LLSpeakerMgr : public LLOldEvents::LLObservable { public: LLSpeakerMgr(LLVoiceChannel* channelp); @@ -237,46 +237,46 @@ public: static void onChangeModerationMode(LLUICtrl* ctrl, void* user_data); protected: - class SpeakerMuteListener : public LLSimpleListener + class SpeakerMuteListener : public LLOldEvents::LLSimpleListener { public: SpeakerMuteListener(LLPanelActiveSpeakers* panel) : mPanel(panel) {} - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); LLPanelActiveSpeakers* mPanel; }; friend class SpeakerAddListener; - class SpeakerAddListener : public LLSimpleListener + class SpeakerAddListener : public LLOldEvents::LLSimpleListener { public: SpeakerAddListener(LLPanelActiveSpeakers* panel) : mPanel(panel) {} - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); LLPanelActiveSpeakers* mPanel; }; friend class SpeakerRemoveListener; - class SpeakerRemoveListener : public LLSimpleListener + class SpeakerRemoveListener : public LLOldEvents::LLSimpleListener { public: SpeakerRemoveListener(LLPanelActiveSpeakers* panel) : mPanel(panel) {} - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); LLPanelActiveSpeakers* mPanel; }; friend class SpeakerClearListener; - class SpeakerClearListener : public LLSimpleListener + class SpeakerClearListener : public LLOldEvents::LLSimpleListener { public: SpeakerClearListener(LLPanelActiveSpeakers* panel) : mPanel(panel) {} - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); LLPanelActiveSpeakers* mPanel; }; diff --git a/indra/newview/llfloateravatarlist.cpp b/indra/newview/llfloateravatarlist.cpp index 7b6d1225f..a43aeb38a 100644 --- a/indra/newview/llfloateravatarlist.cpp +++ b/indra/newview/llfloateravatarlist.cpp @@ -57,6 +57,10 @@ #include "llsdutil.h" +#include "llaudioengine.h" + +#include "llstartup.h" + // #include "llviewermenu.h" // @@ -125,7 +129,8 @@ void chat_avatar_status(std::string name, LLUUID key, ERadarAlertType type, bool LLAvatarListEntry::LLAvatarListEntry(const LLUUID& id, const std::string &name, const LLVector3d &position) : mID(id), mName(name), mPosition(position), mDrawPosition(), mMarked(FALSE), mFocused(FALSE), mUpdateTimer(), mFrame(gFrameCount), mInSimFrame(U32_MAX), mInDrawFrame(U32_MAX), - mInChatFrame(U32_MAX), mInShoutFrame(U32_MAX) + mInChatFrame(U32_MAX), mInShoutFrame(U32_MAX), + mActivityType(ACTIVITY_NEW), mActivityTimer() { } @@ -214,6 +219,26 @@ BOOL LLAvatarListEntry::isDead() { return getEntryAgeSeconds() > DEAD_KEEP_TIME; } +const F32 ACTIVITY_TIMEOUT = 1.0f; +void LLAvatarListEntry::setActivity(ACTIVITY_TYPE activity) +{ + if ( activity >= mActivityType || mActivityTimer.getElapsedTimeF32() > ACTIVITY_TIMEOUT ) + { + mActivityType = activity; + mActivityTimer.start(); + } +} + +LLAvatarListEntry::ACTIVITY_TYPE LLAvatarListEntry::getActivity() +{ + if ( mActivityTimer.getElapsedTimeF32() > ACTIVITY_TIMEOUT ) + { + mActivityType = ACTIVITY_NONE; + } + if(isDead())return ACTIVITY_DEAD; + + return mActivityType; +} LLFloaterAvatarList::LLFloaterAvatarList() : LLFloater(std::string("radar")) { @@ -229,7 +254,7 @@ LLFloaterAvatarList::~LLFloaterAvatarList() void LLFloaterAvatarList::toggle(void*) { // [RLVa:KB] - if(!gRlvHandler.hasBehaviour(RLV_BHVR_SHOWNAMES)) + if(gRlvHandler.hasBehaviour(RLV_BHVR_SHOWNAMES)) { if(instanceExists()) getInstance()->close(); @@ -237,9 +262,13 @@ void LLFloaterAvatarList::toggle(void*) else // [/RLVa:KB] if(!instanceExists() || !getInstance()->getVisible()) + { showInstance(); + } else + { getInstance()->close(); + } } //static @@ -325,6 +354,23 @@ BOOL LLFloaterAvatarList::postBuild() return TRUE; } +void updateParticleActivity(LLDrawable *drawablep) +{ + if (LLFloaterAvatarList::instanceExists()) + { + LLViewerObject *vobj = drawablep->getVObj(); + if (vobj && vobj->isParticleSource()) + { + LLUUID id = vobj->mPartSourcep->getOwnerUUID(); + LLAvatarListEntry *ent = LLFloaterAvatarList::getInstance()->getAvatarEntry(id); + if ( NULL != ent ) + { + ent->setActivity(LLAvatarListEntry::ACTIVITY_PARTICLES); + } + } + } +} + void LLFloaterAvatarList::updateAvatarList() { //llinfos << "radar refresh: updating map" << llendl; @@ -341,6 +387,25 @@ void LLFloaterAvatarList::updateAvatarList() { mUpdate = TRUE; } + //moved to pipeline to prevent a crash + //gPipeline.forAllVisibleDrawables(updateParticleActivity); + + + //todo: make this less of a hacked up copypasta from dales 1.18. + if(gAudiop != NULL) + { + LLAudioEngine::source_map::iterator iter; + for (iter = gAudiop->mAllSources.begin(); iter != gAudiop->mAllSources.end(); ++iter) + { + LLAudioSource *sourcep = iter->second; + LLUUID uuid = sourcep->getOwnerID(); + LLAvatarListEntry *ent = getAvatarEntry(uuid); + if ( ent ) + { + ent->setActivity(LLAvatarListEntry::ACTIVITY_SOUND); + } + } + } LLVector3d mypos = gAgent.getPositionGlobal(); @@ -433,6 +498,7 @@ void LLFloaterAvatarList::updateAvatarList() // Avatar already in list, update position F32 dist = (F32)(position - mypos).magVec(); mAvatars[avid].setPosition(position, (avatarp->getRegion() == gAgent.getRegion()), true, dist < 20.0, dist < 100.0); + if(avatarp->isTyping())mAvatars[avid].setActivity(LLAvatarListEntry::ACTIVITY_TYPING); } else { @@ -777,6 +843,40 @@ void LLFloaterAvatarList::refreshAvatarList() snprintf(temp, sizeof(temp), "%d", (S32)position.mdV[VZ]); } element["columns"][LIST_ALTITUDE]["value"] = temp; + + element["columns"][LIST_ACTIVITY]["column"] = "activity"; + element["columns"][LIST_ACTIVITY]["type"] = "icon"; + + std::string activity_icon = ""; + switch(entry->getActivity()) + { + case LLAvatarListEntry::ACTIVITY_MOVING: + activity_icon = "inv_item_animation.tga"; + break; + case LLAvatarListEntry::ACTIVITY_GESTURING: + activity_icon = "inv_item_gesture.tga"; + break; + case LLAvatarListEntry::ACTIVITY_SOUND: + activity_icon = "inv_item_sound.tga"; + break; + case LLAvatarListEntry::ACTIVITY_REZZING: + activity_icon = "ff_edit_theirs.tga"; + break; + case LLAvatarListEntry::ACTIVITY_PARTICLES: + activity_icon = "particles_scan.tga"; + break; + case LLAvatarListEntry::ACTIVITY_NEW: + activity_icon = "avatar_new.tga"; + break; + case LLAvatarListEntry::ACTIVITY_TYPING: + activity_icon = "avatar_typing.tga"; + break; + default: + break; + } + + element["columns"][LIST_ACTIVITY]["value"] = activity_icon;//icon_image_id; //"icn_active-speakers-dot-lvl0.tga"; + //element["columns"][LIST_AVATAR_ACTIVITY]["color"] = icon_color.getValue(); element["columns"][LIST_CLIENT]["column"] = "client"; element["columns"][LIST_CLIENT]["type"] = "text"; diff --git a/indra/newview/llfloateravatarlist.h b/indra/newview/llfloateravatarlist.h index 872f2159c..44bc3df84 100644 --- a/indra/newview/llfloateravatarlist.h +++ b/indra/newview/llfloateravatarlist.h @@ -33,6 +33,18 @@ class LLAvatarListEntry { public: +enum ACTIVITY_TYPE +{ + ACTIVITY_NONE, /** Avatar not doing anything */ + ACTIVITY_MOVING, /** Changing position */ + ACTIVITY_GESTURING, /** Playing a gesture */ + ACTIVITY_REZZING, /** Rezzing objects */ + ACTIVITY_PARTICLES, /** Creating particles */ + ACTIVITY_TYPING, /** Typing */ + ACTIVITY_NEW, /** Avatar just appeared */ + ACTIVITY_SOUND, /** Playing a sound */ + ACTIVITY_DEAD /** Avatar isn't around anymore, and will be removed soon from the list */ +}; /** * @brief Initializes a list entry * @param id Avatar's key @@ -72,6 +84,13 @@ public: */ LLUUID getID() { return mID; } + void setActivity(ACTIVITY_TYPE activity); + + /** + * @brief Returns the activity type + */ + ACTIVITY_TYPE getActivity(); + /** * @brief Sets the 'focus' status on this entry (camera focused on this avatar) */ @@ -106,8 +125,13 @@ private: /** * @brief Timer to keep track of whether avatars are still there */ + LLTimer mUpdateTimer; + ACTIVITY_TYPE mActivityType; + + LLTimer mActivityTimer; + /** * @brief Last frame when this avatar was updated */ @@ -200,9 +224,11 @@ private: LIST_DISTANCE, LIST_POSITION, LIST_ALTITUDE, + LIST_ACTIVITY, LIST_CLIENT, }; + typedef void (*avlist_command_t)(const LLUUID &avatar, const std::string &name); /** diff --git a/indra/newview/llfloaterblacklist.cpp b/indra/newview/llfloaterblacklist.cpp index 07c0a94cb..dd3443019 100644 --- a/indra/newview/llfloaterblacklist.cpp +++ b/indra/newview/llfloaterblacklist.cpp @@ -11,12 +11,14 @@ #include "llviewerwindow.h" #include "llwindow.h" #include "llviewercontrol.h" +#include "llviewerobjectlist.h" #include "lldate.h" #include "llagent.h" LLFloaterBlacklist* LLFloaterBlacklist::sInstance; std::vector LLFloaterBlacklist::blacklist_textures; +std::vector LLFloaterBlacklist::blacklist_objects; std::map LLFloaterBlacklist::blacklist_entries; @@ -49,11 +51,13 @@ BOOL LLFloaterBlacklist::postBuild() childSetAction("remove_btn", onClickRemove, this); childSetAction("save_btn", onClickSave, this); childSetAction("load_btn", onClickLoad, this); + childSetAction("rerender_btn", onClickRerender, this); childSetVisible("copy_uuid_btn",false); LLComboBox* box = getChild("asset_combo"); box->add("Texture",LLSD(0)); box->add("Sound",LLSD(1)); box->add("Animation",LLSD(20)); + box->add("Object",LLSD(6)); refresh(); return TRUE; } @@ -64,6 +68,7 @@ void LLFloaterBlacklist::refresh() list->clearRows(); for(std::map::iterator iter = blacklist_entries.begin(); iter != blacklist_entries.end(); ++iter) { + if(iter->first.isNull()) continue; LLSD element; std::string agent; gCacheName->getFullName(LLUUID(iter->second["entry_agent"].asString()), agent); @@ -121,10 +126,14 @@ void LLFloaterBlacklist::addEntry(LLUUID key, LLSD data) { if(key.notNull()) { - if(!data.has("entry_type")) + if(!data.has("entry_type")) + { LL_WARNS("FloaterBlacklistAdd") << "addEntry called with no entry type, specify LLAssetType::Etype" << LL_ENDL; + } else if(!data.has("entry_name")) + { LL_WARNS("FloaterBlacklistAdd") << "addEntry called with no entry name, specify the name that should appear in the listing for this entry." << LL_ENDL; + } else { if(!data.has("entry_date")) @@ -137,20 +146,22 @@ void LLFloaterBlacklist::addEntry(LLUUID key, LLSD data) } if(data["entry_type"].asString() == "1") { - //remove sounds - LLUUID sound_id=LLUUID(key); - gVFS->removeFile(sound_id,LLAssetType::AT_SOUND); - std::string wav_path= gDirUtilp->getExpandedFilename(LL_PATH_CACHE,sound_id.asString()) + ".dsf"; - if(LLAPRFile::isExist(wav_path, LL_APR_RPB)) - LLAPRFile::remove(wav_path); - gAudiop->removeAudioData(sound_id); + //remove sounds + LLUUID sound_id=LLUUID(key); + gVFS->removeFile(sound_id,LLAssetType::AT_SOUND); + std::string wav_path= gDirUtilp->getExpandedFilename(LL_PATH_CACHE,sound_id.asString()) + ".dsf"; + if(LLAPRFile::isExist(wav_path, LL_APR_RPB)) + LLAPRFile::remove(wav_path); + gAudiop->removeAudioData(sound_id); } blacklist_entries.insert(std::pair(key,data)); updateBlacklists(); } } else + { LL_WARNS("FloaterBlacklistAdd") << "addEntry called with a null entry key, please specify LLUUID of asset." << LL_ENDL; + } } // static @@ -173,19 +184,19 @@ void LLFloaterBlacklist::onClickRemove(void* user_data) LLScrollListCtrl* list = floaterp->getChild("file_list"); if(list->getFirstSelected()) { - LLScrollListItem* item = list->getFirstSelected(); - LLUUID selected_id(item->getColumn(0)->getValue().asUUID()); - if(selected_id.isNull()) return; - list->deleteSingleItem(list->getFirstSelectedIndex()); - blacklist_entries.erase(selected_id); - updateBlacklists(); - + LLScrollListItem* item = list->getFirstSelected(); + LLUUID selected_id = item->getColumn(0)->getValue().asUUID(); + if(selected_id.isNull()) return; + list->deleteSingleItem(list->getFirstSelectedIndex()); + blacklist_entries.erase(selected_id); + updateBlacklists(); + } } // static void LLFloaterBlacklist::loadFromSave() { - std::string file_name = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "floater_blist_settings.xml"); + std::string file_name = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "blacklist_sg1.xml"); llifstream xml_file(file_name); if(!xml_file.is_open()) return; LLSD data; @@ -206,6 +217,7 @@ void LLFloaterBlacklist::updateBlacklists() if(gAssetStorage) { blacklist_textures.clear(); + blacklist_objects.clear(); gAssetStorage->mBlackListedAsset.clear(); for(std::map::iterator iter = blacklist_entries.begin(); iter != blacklist_entries.end(); ++iter) { @@ -217,6 +229,12 @@ void LLFloaterBlacklist::updateBlacklists() { gAssetStorage->mBlackListedAsset.push_back(LLUUID(iter->first)); } + + if(blacklist_entries[iter->first]["entry_type"].asString() == "6") + { + blacklist_objects.push_back(LLUUID(iter->first)); + } + } saveToDisk(); LLFloaterBlacklist* instance = LLFloaterBlacklist::getInstance(); @@ -228,7 +246,7 @@ void LLFloaterBlacklist::updateBlacklists() //static void LLFloaterBlacklist::saveToDisk() { - std::string file_name = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "floater_blist_settings.xml"); + std::string file_name = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "blacklist_sg1.xml"); llofstream export_file(file_name); LLSD data; for(std::map::iterator iter = blacklist_entries.begin(); iter != blacklist_entries.end(); ++iter) @@ -259,7 +277,7 @@ void LLFloaterBlacklist::onClickSave_continued(AIFilePicker* filepicker) { data[iter->first.asString()] = iter->second; } - LLSDSerialize::toPrettyXML(data, export_file); + LLSDSerialize::toPrettyXML(data, export_file); export_file.close(); } } @@ -291,4 +309,25 @@ void LLFloaterBlacklist::onClickLoad_continued(AIFilePicker* filepicker) xml_file.close(); } } + + +void LLFloaterBlacklist::onClickRerender(void* user_data) +{ + std::map blacklist_new; + for(std::map::iterator itr = blacklist_entries.begin(); itr != blacklist_entries.end(); ++itr) + { + if(blacklist_entries[itr->first]["entry_type"].asString() == "6") continue; + blacklist_new[itr->first] = blacklist_entries[itr->first]; + blacklist_new[itr->second] = blacklist_entries[itr->second]; + } + blacklist_entries = blacklist_new; + saveToDisk(); + LLFloaterBlacklist* instance = LLFloaterBlacklist::getInstance(); + if(instance) + { + instance->refresh(); + } + +} + // diff --git a/indra/newview/llfloaterblacklist.h b/indra/newview/llfloaterblacklist.h index c718fd71d..6851e1a1e 100644 --- a/indra/newview/llfloaterblacklist.h +++ b/indra/newview/llfloaterblacklist.h @@ -32,6 +32,7 @@ public: static std::map blacklist_entries; static std::vector blacklist_textures; + static std::vector blacklist_objects; static void loadFromSave(); @@ -48,6 +49,7 @@ private: static void onClickSave_continued(AIFilePicker* filepicker); static void onClickLoad(void* user_data); static void onClickLoad_continued(AIFilePicker* filepicker); + static void onClickRerender(void* user_data); static void onClickCopyUUID(void* user_data); static void onClickRemove(void* user_data); diff --git a/indra/newview/llfloaterbuyland.cpp b/indra/newview/llfloaterbuyland.cpp index c80708879..4926453e0 100644 --- a/indra/newview/llfloaterbuyland.cpp +++ b/indra/newview/llfloaterbuyland.cpp @@ -540,7 +540,7 @@ void LLFloaterBuyLandUI::updateCovenantInfo() LLTextBox* region_type = getChild("region_type_text"); if (region_type) { - region_type->setText(region->getSimProductName()); + region_type->setText(region->getLocalizedSimProductName()); } LLTextBox* resellable_clause = getChild("resellable_clause"); diff --git a/indra/newview/llfloatergroups.cpp b/indra/newview/llfloatergroups.cpp index 0e2f8d6d0..ddc601f9d 100644 --- a/indra/newview/llfloatergroups.cpp +++ b/indra/newview/llfloatergroups.cpp @@ -62,6 +62,8 @@ #include "hippolimits.h" +using namespace LLOldEvents; + // static std::map LLFloaterGroupPicker::sInstances; diff --git a/indra/newview/llfloatergroups.h b/indra/newview/llfloatergroups.h index c0d06e7c5..5311809eb 100644 --- a/indra/newview/llfloatergroups.h +++ b/indra/newview/llfloatergroups.h @@ -84,14 +84,14 @@ protected: static instance_map_t sInstances; }; -class LLPanelGroups : public LLPanel, public LLSimpleListener +class LLPanelGroups : public LLPanel, public LLOldEvents::LLSimpleListener { public: LLPanelGroups(); virtual ~LLPanelGroups(); //LLEventListener - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); // clear the group list, and get a fresh set of info. void reset(); diff --git a/indra/newview/llfloaterland.cpp b/indra/newview/llfloaterland.cpp index 1cc3fc820..9f9e2d308 100644 --- a/indra/newview/llfloaterland.cpp +++ b/indra/newview/llfloaterland.cpp @@ -525,7 +525,7 @@ void LLPanelLandGeneral::refresh() if (regionp) { mContentRating->setText(regionp->getSimAccessString()); - mLandType->setText(regionp->getSimProductName()); + mLandType->setText(regionp->getLocalizedSimProductName()); } // estate owner/manager cannot edit other parts of the parcel @@ -2907,7 +2907,7 @@ void LLPanelLandCovenant::refresh() LLTextBox* region_landtype = getChild("region_landtype_text"); if (region_landtype) { - region_landtype->setText(region->getSimProductName()); + region_landtype->setText(region->getLocalizedSimProductName()); } LLTextBox* region_maturity = getChild("region_maturity_text"); diff --git a/indra/newview/llfloaterregioninfo.cpp b/indra/newview/llfloaterregioninfo.cpp index 54ab0701d..794e774fe 100644 --- a/indra/newview/llfloaterregioninfo.cpp +++ b/indra/newview/llfloaterregioninfo.cpp @@ -2720,7 +2720,7 @@ bool LLPanelEstateCovenant::refreshFromRegion(LLViewerRegion* region) LLTextBox* region_landtype = getChild("region_landtype_text"); if (region_landtype) { - region_landtype->setText(region->getSimProductName()); + region_landtype->setText(region->getLocalizedSimProductName()); } diff --git a/indra/newview/llfloaterreporter.cpp b/indra/newview/llfloaterreporter.cpp index dccb8ab89..5888bb80f 100644 --- a/indra/newview/llfloaterreporter.cpp +++ b/indra/newview/llfloaterreporter.cpp @@ -47,7 +47,7 @@ #include "llimagej2c.h" #include "llstring.h" #include "llsys.h" -#include "llversionviewer.h" +#include "sgversion.h" #include "message.h" #include "v3math.h" @@ -107,7 +107,6 @@ LLMap< EReportType, LLFloaterReporter* > gReporterInstances; // keeps track of where email is going to - global to avoid a pile // of static/non-static access outside my control namespace { - static BOOL gEmailToEstateOwner = FALSE; static BOOL gDialogVisible = FALSE; } @@ -227,16 +226,10 @@ void LLFloaterReporter::processRegionInfo(LLMessageSystem* msg) { U32 region_flags; msg->getU32("RegionInfo", "RegionFlags", region_flags); - gEmailToEstateOwner = ( region_flags & REGION_FLAGS_ABUSE_EMAIL_TO_ESTATE_OWNER ); if ( gDialogVisible ) { - if ( gEmailToEstateOwner ) - { - LLNotificationsUtil::add("HelpReportAbuseEmailEO"); - } - else - LLNotificationsUtil::add("HelpReportAbuseEmailLL"); + LLNotificationsUtil::add("HelpReportAbuseEmailLL"); }; } @@ -264,18 +257,7 @@ LLFloaterReporter::~LLFloaterReporter() // virtual void LLFloaterReporter::draw() { - // this is set by a static callback sometime after the dialog is created. - // Only disable screenshot for abuse reports to estate owners - bug reports always - // allow screenshots to be taken. - if ( gEmailToEstateOwner && ( mReportType != BUG_REPORT ) ) - { - childSetValue("screen_check", FALSE ); - childSetEnabled("screen_check", FALSE ); - } - else - { - childSetEnabled("screen_check", TRUE ); - } + childSetEnabled("screen_check", TRUE ); LLFloater::draw(); } @@ -747,10 +729,10 @@ LLSD LLFloaterReporter::gatherReport() if ( mReportType == BUG_REPORT) { - summary << short_platform << " V" << LL_VERSION_MAJOR << "." - << LL_VERSION_MINOR << "." - << LL_VERSION_PATCH << "." - << LL_VERSION_BUILD + summary << short_platform << " V" << gVersionMajor << "." + << gVersionMinor << "." + << gVersionPatch << "." + << gVersionBuild << " (" << regionp->getName() << ")" << "[" << category_name << "] " << "\"" << childGetValue("summary_edit").asString() << "\""; @@ -768,10 +750,10 @@ LLSD LLFloaterReporter::gatherReport() std::ostringstream details; if (mReportType != BUG_REPORT) { - details << "V" << LL_VERSION_MAJOR << "." // client version moved to body of email for abuse reports - << LL_VERSION_MINOR << "." - << LL_VERSION_PATCH << "." - << LL_VERSION_BUILD << std::endl << std::endl; + details << "V" << gVersionMajor << "." // client version moved to body of email for abuse reports + << gVersionMinor << "." + << gVersionPatch << "." + << gVersionBuild << std::endl << std::endl; } std::string object_name = childGetText("object_name"); std::string owner_name = childGetText("owner_name"); @@ -792,9 +774,9 @@ LLSD LLFloaterReporter::gatherReport() std::string version_string; version_string = llformat( "%d.%d.%d %s %s %s %s", - LL_VERSION_MAJOR, - LL_VERSION_MINOR, - LL_VERSION_PATCH, + gVersionMajor, + gVersionMinor, + gVersionPatch, platform, gSysCPU.getFamily().c_str(), gGLManager.mGLRenderer.c_str(), @@ -805,17 +787,7 @@ LLSD LLFloaterReporter::gatherReport() LLUUID screenshot_id = LLUUID::null; if (childGetValue("screen_check")) { - if ( mReportType != BUG_REPORT ) - { - if ( gEmailToEstateOwner == FALSE ) - { - screenshot_id = childGetValue("screenshot"); - } - } - else - { - screenshot_id = childGetValue("screenshot"); - }; + screenshot_id = childGetValue("screenshot"); }; LLSD report = LLSD::emptyMap(); diff --git a/indra/newview/llfloaterstats.cpp b/indra/newview/llfloaterstats.cpp index 459aa3585..0367db8fe 100644 --- a/indra/newview/llfloaterstats.cpp +++ b/indra/newview/llfloaterstats.cpp @@ -45,6 +45,7 @@ #include "pipeline.h" #include "llviewerobjectlist.h" #include "llviewertexturelist.h" +#include "sgmemstat.h" const S32 LL_SCROLL_BORDER = 1; @@ -99,7 +100,17 @@ void LLFloaterStats::buildStats() stat_barp->mLabelSpacing = 200.f; stat_barp->mPerSec = FALSE; stat_barp->mDisplayMean = FALSE; - + + if(SGMemStat::haveStat()) { + stat_barp = stat_viewp->addStat("Allocated memory", &(LLViewerStats::getInstance()->mMallocStat), "DebugStatModeMalloc"); + stat_barp->setUnitLabel(" MB"); + stat_barp->mMinBar = 0.f; + stat_barp->mMaxBar = 4000.f; + stat_barp->mTickSpacing = 100.f; + stat_barp->mLabelSpacing = 200.f; + stat_barp->mPerSec = FALSE; + stat_barp->mDisplayMean = FALSE; + } stat_viewp = new LLStatView("advanced stat view", "Advanced", "OpenDebugStatAdvanced", rect); addStatView(stat_viewp); diff --git a/indra/newview/llfloaterwindlight.cpp b/indra/newview/llfloaterwindlight.cpp index 390c8f581..e2c96db76 100644 --- a/indra/newview/llfloaterwindlight.cpp +++ b/indra/newview/llfloaterwindlight.cpp @@ -640,7 +640,7 @@ void LLFloaterWindLight::onColorControlIMoved(LLUICtrl* ctrl, void* userData) if(color_ctrl->isSunOrAmbientColor) scale = WL_SUN_AMBIENT_SLIDER_SCALE; else if(color_ctrl->isBlueHorizonOrDensity) - WL_BLUE_HORIZON_DENSITY_SCALE; + scale = WL_BLUE_HORIZON_DENSITY_SCALE; F32 iVal = color_ctrl->i * scale; @@ -1070,5 +1070,13 @@ void LLFloaterWindLight::populateSkyPresetsList() mSkyPresetCombo->add(*it); } + LLWLParamManager::preset_name_list_t user_presets; + LLWLParamManager::getInstance()->getUserPresetNames(user_presets); + + for (LLWLParamManager::preset_name_list_t::const_iterator it = user_presets.begin(); it != user_presets.end(); ++it) + { + mSkyPresetCombo->add(*it); + } + mSkyPresetCombo->selectByValue(LLEnvManagerNew::instance().getSkyPresetName()); } diff --git a/indra/newview/llinventoryactions.cpp b/indra/newview/llinventoryactions.cpp index 78177074f..a4cdfde10 100644 --- a/indra/newview/llinventoryactions.cpp +++ b/indra/newview/llinventoryactions.cpp @@ -100,6 +100,8 @@ #include "statemachine/aifilepicker.h" // +using namespace LLOldEvents; + const std::string NEW_LSL_NAME = "New Script"; // *TODO:Translate? (probably not) const std::string NEW_NOTECARD_NAME = "New Note"; // *TODO:Translate? (probably not) const std::string NEW_GESTURE_NAME = "New Gesture"; // *TODO:Translate? (probably not) diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index 736a8a224..e8c78fd7a 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -147,6 +147,8 @@ struct LLMoveInv void* mUserData; }; +using namespace LLOldEvents; + // Helpers // bug in busy count inc/dec right now, logic is complex... do we really need it? void inc_busy_count() @@ -2098,7 +2100,7 @@ void warn_move_inventory(LLViewerObject* object, LLMoveInv* move_inv) // Move/copy all inventory items from the Contents folder of an in-world // object to the agent's inventory, inside a given category. -BOOL move_inv_category_world_to_agent(const LLUUID& object_id, +BOOL move_inv_category_world_to_agent(const LLUUID& object_id, const LLUUID& category_id, BOOL drop, void (*callback)(S32, void*), @@ -2125,7 +2127,7 @@ BOOL move_inv_category_world_to_agent(const LLUUID& object_id, llinfos << "Object contents not found for drop." << llendl; return FALSE; } - + BOOL accept = TRUE; BOOL is_move = FALSE; diff --git a/indra/newview/llnetmap.cpp b/indra/newview/llnetmap.cpp index d4b2d10fb..86294d46b 100644 --- a/indra/newview/llnetmap.cpp +++ b/indra/newview/llnetmap.cpp @@ -80,6 +80,8 @@ #include "rlvhandler.h" // [/RLVa:KB] +using namespace LLOldEvents; + const F32 MAP_SCALE_MIN = 32; const F32 MAP_SCALE_MID = 256; const F32 MAP_SCALE_MAX = 4096; diff --git a/indra/newview/llnetmap.h b/indra/newview/llnetmap.h index 2efae83dd..a709761d6 100644 --- a/indra/newview/llnetmap.h +++ b/indra/newview/llnetmap.h @@ -130,55 +130,55 @@ private: class LLScaleMap : public LLMemberListener { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class LLCenterMap : public LLMemberListener { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class LLCheckCenterMap : public LLMemberListener { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class LLRotateMap : public LLMemberListener { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class LLCheckRotateMap : public LLMemberListener { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class LLStopTracking : public LLMemberListener { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class LLEnableTracking : public LLMemberListener { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class LLShowAgentProfile : public LLMemberListener { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class LLCamFollow : public LLMemberListener //moymod { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; @@ -188,37 +188,37 @@ private: class mmsetred : public LLMemberListener //moymod { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class mmsetgreen : public LLMemberListener //moymod { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class mmsetblue : public LLMemberListener //moymod { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class mmsetyellow : public LLMemberListener //moymod { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class mmsetcustom : public LLMemberListener //moymod { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class mmsetunmark : public LLMemberListener //moymod { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class mmenableunmark : public LLMemberListener //moymod { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; @@ -228,7 +228,7 @@ private: class LLEnableProfile : public LLMemberListener { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; }; diff --git a/indra/newview/llnotify.cpp b/indra/newview/llnotify.cpp index 20145004f..c942aee44 100644 --- a/indra/newview/llnotify.cpp +++ b/indra/newview/llnotify.cpp @@ -242,6 +242,30 @@ LLNotifyBox::LLNotifyBox(LLNotificationPtr notification, // it appears below the caution textbox y = y - caution_height; } + else if (mIsCaution && mIsTip) + { + + const S32 BTN_TOP = BOTTOM_PAD + (((mNumOptions-1+2)/3)) * (BTN_HEIGHT+VPAD); + + // Tokenization on \n is handled by LLTextBox + + const S32 MAX_LENGTH = 512 + 20 + DB_FIRST_NAME_BUF_SIZE + DB_LAST_NAME_BUF_SIZE + DB_INV_ITEM_NAME_BUF_SIZE; // For script dialogs: add space for title. + + text = new LLTextEditor(std::string("box"), LLRect(x, y, getRect().getWidth()-2, mIsTip ? BOTTOM : BTN_TOP+16), MAX_LENGTH, mMessage, sFont, FALSE); + text->setWordWrap(TRUE); + text->setTabStop(FALSE); + text->setMouseOpaque(FALSE); + text->setBorderVisible(FALSE); + text->setTakesNonScrollClicks(FALSE); + text->setHideScrollbarForShortDocs(TRUE); + text->setReadOnlyBgColor ( LLColor4::transparent ); // the background color of the box is manually + // rendered under the text box, therefore we want + // the actual text box to be transparent + text->setReadOnlyFgColor ( gColors.getColor("NotifyCautionWarnColor") ); //sets caution text color for tip notifications + text->setEnabled(FALSE); // makes it read-only + text->setTabStop(FALSE); // can't tab to it (may be a problem for scrolling via keyboard) + addChild(text); + } else { diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp index 949cbe9b8..045b722a4 100644 --- a/indra/newview/llpanellogin.cpp +++ b/indra/newview/llpanellogin.cpp @@ -42,7 +42,7 @@ #include "llfontgl.h" #include "llmd5.h" #include "llsecondlifeurls.h" -#include "llversionviewer.h" +#include "sgversion.h" #include "v4color.h" #include "llbutton.h" @@ -347,12 +347,12 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect, childSetAction("grids_btn", onClickGrids, this); childSetCommitCallback("grids_combo", onSelectGrid, this); - std::string channel = LL_CHANNEL; + std::string channel = gVersionChannel; std::string version = llformat("%d.%d.%d (%d)", - LL_VERSION_MAJOR, - LL_VERSION_MINOR, - LL_VERSION_PATCH, + gVersionMajor, + gVersionMinor, + gVersionPatch, LL_VIEWER_BUILD ); LLTextBox* channel_text = getChild("channel_text"); channel_text->setTextArg("[CHANNEL]", channel); // though not displayed @@ -861,7 +861,7 @@ void LLPanelLogin::updateGridCombo() } HippoGridManager::GridIterator it, end = gHippoGridManager->endGrid(); for (it = gHippoGridManager->beginGrid(); it != end; ++it) { - const std::string &grid = it->second->getGridName(); + std::string grid = it->second->getGridName(); if (grid != defaultGrid) { grids->add(grid); if (grid == currentGrid) selectIndex = i; @@ -934,10 +934,10 @@ void LLPanelLogin::loadLoginPage() } std::string version = llformat("%d.%d.%d (%d)", - LL_VERSION_MAJOR, LL_VERSION_MINOR, LL_VERSION_PATCH, LL_VERSION_BUILD); + gVersionMajor, gVersionMinor, gVersionPatch, gVersionBuild); if(login_page.find("secondlife.com") == -1) { - oStr << "&channel=" << LLWeb::curlEscape(LL_CHANNEL); + oStr << "&channel=" << LLWeb::curlEscape(gVersionChannel); oStr << "&version=" << LLWeb::curlEscape(version); } @@ -959,7 +959,10 @@ void LLPanelLogin::loadLoginPage() } } } - else + else if (gHippoGridManager->getConnectedGrid()->isOpenSimulator()){ + oStr << "&grid=" << gHippoGridManager->getConnectedGrid()->getGridNick(); + } + else if (gHippoGridManager->getConnectedGrid()->getPlatform() == HippoGridInfo::PLATFORM_AURORA) { oStr << "&grid=" << LLWeb::curlEscape(LLViewerLogin::getInstance()->getGridLabel()); } diff --git a/indra/newview/llprefsim.cpp b/indra/newview/llprefsim.cpp index 62dc37726..e0eee0c72 100644 --- a/indra/newview/llprefsim.cpp +++ b/indra/newview/llprefsim.cpp @@ -198,7 +198,7 @@ void LLPrefsIMImpl::apply() } else { - gDirUtilp->setPerAccountChatLogsDir(gHippoGridManager->getCurrentGridNick(), + gDirUtilp->setPerAccountChatLogsDir(gHippoGridManager->getConnectedGrid()->getGridNick(), gSavedSettings.getString("FirstName"), gSavedSettings.getString("LastName") ); } diff --git a/indra/newview/llpreviewscript.cpp b/indra/newview/llpreviewscript.cpp index 424f2a902..0bd4c3ea0 100644 --- a/indra/newview/llpreviewscript.cpp +++ b/indra/newview/llpreviewscript.cpp @@ -2301,6 +2301,13 @@ void LLLiveLSLEditor::saveIfNeeded() return; } +// [RLVa:KB] - Checked: 2010-11-25 (RLVa-1.2.2b) | Modified: RLVa-1.2.2b + if ( (rlv_handler_t::isEnabled()) && (gRlvAttachmentLocks.isLockedAttachment(object->getRootEdit())) ) + { + return; + } +// [/RLVa:KB] + // get the latest info about it. We used to be losing the script // name on save, because the viewer object version of the item, // and the editor version would get out of synch. Here's a good @@ -2607,14 +2614,6 @@ void LLLiveLSLEditor::onSave(void* userdata, BOOL close_after_save) { LLLiveLSLEditor* self = (LLLiveLSLEditor*)userdata; -// [RLVa:KB] - Checked: 2010-09-28 (RLVa-1.2.1f) | Modified: RLVa-1.0.5a - const LLViewerObject* pObject = gObjectList.findObject(self->mObjectID); - if ( (rlv_handler_t::isEnabled()) && (gRlvAttachmentLocks.isLockedAttachment(pObject->getRootEdit())) ) - { - return; - } -// [/RLVa:KB] - self->mCloseAfterSave = close_after_save; self->saveIfNeeded(); } diff --git a/indra/newview/llpreviewtexture.cpp b/indra/newview/llpreviewtexture.cpp index 97c929956..8bcf0dd2a 100644 --- a/indra/newview/llpreviewtexture.cpp +++ b/indra/newview/llpreviewtexture.cpp @@ -469,7 +469,7 @@ LLUUID LLPreviewTexture::getItemID() if ((perms & PERM_TRANSFER) && (perms & PERM_COPY)) { - return item->getUUID(); + return item->getAssetUUID(); } } return LLUUID::null; diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 9ebce0906..bd584bc6c 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -85,7 +85,7 @@ #include "llsecondlifeurls.h" #include "llstring.h" #include "lluserrelations.h" -#include "llversionviewer.h" +#include "sgversion.h" #include "llvfs.h" #include "llxorcipher.h" // saved password, MAC address #include "message.h" @@ -535,9 +535,9 @@ bool idle_startup() if(!start_messaging_system( message_template_path, port, - LL_VERSION_MAJOR, - LL_VERSION_MINOR, - LL_VERSION_PATCH, + gVersionMajor, + gVersionMinor, + gVersionPatch, FALSE, std::string(), responder, @@ -1021,7 +1021,7 @@ bool idle_startup() } else { - gDirUtilp->setLindenUserDir(gHippoGridManager->getCurrentGridNick(), firstname, lastname); + gDirUtilp->setLindenUserDir(gHippoGridManager->getConnectedGrid()->getGridNick(), firstname, lastname); } LLFile::mkdir(gDirUtilp->getLindenUserDir()); @@ -1061,7 +1061,7 @@ bool idle_startup() } else { - gDirUtilp->setPerAccountChatLogsDir(gHippoGridManager->getCurrentGridNick(), + gDirUtilp->setPerAccountChatLogsDir(gHippoGridManager->getConnectedGrid()->getGridNick(), gSavedSettings.getString("FirstName"), gSavedSettings.getString("LastName") ); } LLFile::mkdir(gDirUtilp->getChatLogsDir()); @@ -2046,7 +2046,7 @@ bool idle_startup() //--------------------------------------------------------------------- if(STATE_SEED_GRANTED_WAIT == LLStartUp::getStartupState()) { - /*LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(gFirstSimHandle); + LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(gFirstSimHandle); if (regionp->capabilitiesReceived()) { LLStartUp::setStartupState( STATE_SEED_CAP_GRANTED ); @@ -2064,7 +2064,7 @@ bool idle_startup() { set_startup_status(0.4f, LLTrans::getString("LoginRequestSeedCapGrant"), gAgent.mMOTD); } - }*/ + } return FALSE; } @@ -3410,7 +3410,7 @@ bool update_dialog_callback(const LLSD& notification, const LLSD& response) // userserver no longer exists. query_map["userserver"] = LLViewerLogin::getInstance()->getGridLabel(); // - query_map["channel"] = LL_CHANNEL; + query_map["channel"] = gVersionChannel; // *TODO constantize this guy // *NOTE: This URL is also used in win_setup/lldownloader.cpp @@ -3485,7 +3485,7 @@ bool update_dialog_callback(const LLSD& notification, const LLSD& response) LLAppViewer::sUpdaterInfo->mUpdateExePath += "\" -name \""; LLAppViewer::sUpdaterInfo->mUpdateExePath += LLAppViewer::instance()->getSecondLifeTitle(); LLAppViewer::sUpdaterInfo->mUpdateExePath += "\" -bundleid \""; - LLAppViewer::sUpdaterInfo->mUpdateExePath += LL_VERSION_BUNDLE_ID; + LLAppViewer::sUpdaterInfo->mUpdateExePath += gVersionBundleID; LLAppViewer::sUpdaterInfo->mUpdateExePath += "\" &"; LL_DEBUGS("AppInit") << "Calling updater: " << LLAppViewer::sUpdaterInfo->mUpdateExePath << LL_ENDL; diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index af8847436..0b30ae83c 100644 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -1358,6 +1358,7 @@ bool LLTextureFetchWorker::doWork(S32 param) //roll back to try UDP if(mCanUseNET) { + resetFormattedData(); mState = INIT ; mCanUseHTTP = false ; setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); @@ -1405,6 +1406,7 @@ bool LLTextureFetchWorker::doWork(S32 param) //roll back to try UDP if(mCanUseNET) { + resetFormattedData(); mState = INIT ; mCanUseHTTP = false ; setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); diff --git a/indra/newview/lltranslate.cpp b/indra/newview/lltranslate.cpp index 501dfe29b..5a537ae95 100644 --- a/indra/newview/lltranslate.cpp +++ b/indra/newview/lltranslate.cpp @@ -35,7 +35,7 @@ #include "llbufferstream.h" #include "lltranslate.h" #include "llui.h" -#include "llversionviewer.h" +#include "sgversion.h" #include "llweb.h" // diff --git a/indra/newview/lltranslate.h b/indra/newview/lltranslate.h index 2723c43ff..5fd85f534 100644 --- a/indra/newview/lltranslate.h +++ b/indra/newview/lltranslate.h @@ -35,7 +35,7 @@ #include "llhttpclient.h" #include "llbufferstream.h" -#include "jsoncpp/reader.h" +#include "json/reader.h" class LLTranslate { diff --git a/indra/newview/lluserauth.cpp b/indra/newview/lluserauth.cpp index 308c9f652..cd9716a7f 100644 --- a/indra/newview/lluserauth.cpp +++ b/indra/newview/lluserauth.cpp @@ -38,7 +38,7 @@ #include #include "lldir.h" -#include "llversionviewer.h" +#include "sgversion.h" #include "llappviewer.h" #include "llviewerbuild.h" #include "llviewercontrol.h" @@ -131,7 +131,7 @@ void LLUserAuth::authenticate( XMLRPC_VectorAppendString(params, "web_login_key", web_login_key.getString().c_str(), 0); XMLRPC_VectorAppendString(params, "start", start.c_str(), 0); XMLRPC_VectorAppendString(params, "version", gCurrentVersion.c_str(), 0); // Includes channel name - XMLRPC_VectorAppendString(params, "channel", LL_CHANNEL, 0); + XMLRPC_VectorAppendString(params, "channel", gVersionChannel, 0); XMLRPC_VectorAppendString(params, "platform", PLATFORM_STRING, 0); XMLRPC_VectorAppendString(params, "mac", hashed_mac.c_str(), 0); @@ -219,7 +219,7 @@ void LLUserAuth::authenticate( XMLRPC_VectorAppendString(params, "passwd", dpasswd.c_str(), 0); XMLRPC_VectorAppendString(params, "start", start.c_str(), 0); XMLRPC_VectorAppendString(params, "version", gCurrentVersion.c_str(), 0); // Includes channel name - XMLRPC_VectorAppendString(params, "channel", LL_CHANNEL, 0); + XMLRPC_VectorAppendString(params, "channel", gVersionChannel, 0); XMLRPC_VectorAppendString(params, "platform", PLATFORM_STRING, 0); XMLRPC_VectorAppendString(params, "mac", hashed_mac.c_str(), 0); diff --git a/indra/newview/llviewerbuild.h b/indra/newview/llviewerbuild.h old mode 100644 new mode 100755 index b93c45c30..b02bdece5 --- a/indra/newview/llviewerbuild.h +++ b/indra/newview/llviewerbuild.h @@ -30,8 +30,8 @@ * $/LicenseInfo$ */ -#include "llversionviewer.h" +#include "sgversion.h" // Set the build number in indra/llcommon/llversionviewer.h! -const S32 LL_VIEWER_BUILD = LL_VERSION_BUILD; +const S32 LL_VIEWER_BUILD = gVersionBuild; diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index 6b24bcde8..811849fe5 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -70,7 +70,7 @@ #include "llnotify.h" #include "llkeyboard.h" #include "llerrorcontrol.h" -#include "llversionviewer.h" +#include "sgversion.h" #include "llappviewer.h" #include "llvosurfacepatch.h" #include "llvowlsky.h" diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index e328bbc66..ac0f7d1f5 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -1060,7 +1060,7 @@ void copy_inventory_from_notecard(const LLUUID& destination_id, // Fallback to the agents region if for some reason the // object isn't found in the viewer. - if(!viewer_region) + if (! viewer_region) { viewer_region = gAgent.getRegion(); } @@ -1090,7 +1090,10 @@ void copy_inventory_from_notecard(const LLUUID& destination_id, body["folder-id"] = destination_id; body["callback-id"] = (LLSD::Integer)callback_id; - LLHTTPClient::post(url, body, new LLHTTPClient::Responder() ); + request["message"] = "CopyInventoryFromNotecard"; + request["payload"] = body; + + viewer_region->getCapAPI().post(request); } void create_new_item(const std::string& name, diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 77966f665..e9f6da192 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -263,6 +263,7 @@ #include "hippogridmanager.h" +using namespace LLOldEvents; using namespace LLVOAvatarDefines; void init_client_menu(LLMenuGL* menu); void init_server_menu(LLMenuGL* menu); @@ -2243,25 +2244,78 @@ class LLObjectDerender : public view_listener_t { bool handleEvent(LLPointer event, const LLSD& userdata) { - LLViewerObject* slct = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); + LLViewerObject* slct = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); if(!slct)return true; + LLUUID id = slct->getID(); LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); LLUUID root_key; - LLSelectNode* node = selection->getFirstRootNode(); - if(node)root_key = node->getObject()->getID(); - if(root_key.notNull()) + //delivers null in linked parts if used as getFirstRootNode() + LLSelectNode* node = selection->getFirstRootNode(NULL,TRUE); + + /*this works for derendering entire object if child is selected + + LLSelectNode* node = selection->getFirstNode(); + //Delivers node even when linked parts, but only first node + + LLViewerObject* obj = node->getObject(); + LLViewerObject* parent = (LLViewerObject*)obj->getParent();*/ + + + if(node) { - id = root_key; - //LLSelectMgr::getInstance()->removeObjectFromSelections(root_key); + root_key = node->getObject()->getID(); + llinfos << "Derender node has key " << root_key << llendl; } - LLSelectMgr::getInstance()->removeObjectFromSelections(id); - - // ...don't kill the avatar - //if (!(id == gAgentID)) - // Kill 'em all - if (true) + else { + llinfos << "Derender node is null " << llendl; + } + + LLViewerRegion* cur_region = gAgent.getRegion(); + std::string entry_name; + if(slct->isAvatar()){ + LLNameValue* firstname = slct->getNVPair("FirstName"); + LLNameValue* lastname = slct->getNVPair("LastName"); + entry_name = llformat("Derendered: (AV) %s %s",firstname->getString(),lastname->getString()); + } + else{ + if(root_key.isNull()) + { + return true; + } + id = root_key; + if(!node->mName.empty()) + { + if(cur_region) + entry_name = llformat("Derendered: %s in region %s",node->mName.c_str(),cur_region->getName().c_str()); + else + entry_name = llformat("Derendered: %s",node->mName.c_str()); + } + else + { + if(cur_region) + entry_name = llformat("Derendered: (unkown object) in region %s",cur_region->getName().c_str()); + else + entry_name = "Derendered: (unkown object)"; + + } + } + + + + + LLSD indata; + indata["entry_type"] = 6; //AT_TEXTURE + indata["entry_name"] = entry_name; + indata["entry_agent"] = gAgent.getID(); + + + // ...don't kill the avatar + if (!(id == gAgentID)) + { + LLFloaterBlacklist::addEntry(id,indata); + LLSelectMgr::getInstance()->deselectAll(); LLViewerObject *objectp = gObjectList.findObject(id); if (objectp) { diff --git a/indra/newview/llviewermenu.h b/indra/newview/llviewermenu.h index a6d96c08c..4a32dfe26 100644 --- a/indra/newview/llviewermenu.h +++ b/indra/newview/llviewermenu.h @@ -99,6 +99,7 @@ bool toggle_build_mode(); void handle_object_build(void*); void handle_save_snapshot(void *); void handle_toggle_flycam(); +void handle_fake_away_status(void*); bool handle_sit_or_stand(); bool handle_give_money_dialog(); diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp index 9c08adf8e..63a9e002a 100644 --- a/indra/newview/llviewermenufile.cpp +++ b/indra/newview/llviewermenufile.cpp @@ -96,6 +96,8 @@ #include "importtracker.h" +using namespace LLOldEvents; + std::deque gUploadQueue; typedef LLMemberListener view_listener_t; diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index e958e99c7..5c5e6de60 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -147,7 +147,6 @@ #include "llfloaterworldmap.h" #include "llviewerdisplay.h" #include "llkeythrottle.h" -#include "lltranslate.h" // #include "llviewernetwork.h" // @@ -165,7 +164,7 @@ #include "hippolimits.h" #include "hipporestrequest.h" #include "hippofloaterxml.h" -#include "llversionviewer.h" +#include "sgversion.h" #include "m7wlinterface.h" #include "llwlparammanager.h" @@ -3251,6 +3250,7 @@ void process_decline_callingcard(LLMessageSystem* msg, void**) LLNotificationsUtil::add("CallingCardDeclined"); } +#if 0 // Google translate doesn't work anymore class ChatTranslationReceiver : public LLTranslate::TranslationReceiver { public : @@ -3293,6 +3293,7 @@ private: LLChat *m_chat; const BOOL m_history; }; +#endif void add_floater_chat(const LLChat &chat, const BOOL history) { @@ -3308,6 +3309,7 @@ void add_floater_chat(const LLChat &chat, const BOOL history) } } +#if 0 // Google translate doesn't work anymore void check_translate_chat(const std::string &mesg, LLChat &chat, const BOOL history) { const bool translate = LLUI::sConfigGroup->getBOOL("TranslateChat"); @@ -3328,6 +3330,7 @@ void check_translate_chat(const std::string &mesg, LLChat &chat, const BOOL hist add_floater_chat(chat, history); } } +#endif // defined in llchatbar.cpp, but not declared in any header void send_chat_from_viewer(std::string utf8_out_text, EChatType type, S32 channel); @@ -3515,7 +3518,7 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) // hello from object if (from_id.isNull()) return; char buf[200]; - snprintf(buf, 200, "%s v%d.%d.%d", LL_CHANNEL, LL_VERSION_MAJOR, LL_VERSION_MINOR, LL_VERSION_PATCH); + snprintf(buf, 200, "%s v%d.%d.%d", gVersionChannel, gVersionMajor, gVersionMinor, gVersionPatch); send_chat_from_viewer(buf, CHAT_TYPE_WHISPER, 427169570); gChatObjectAuth[from_id] = 1; } else if (gChatObjectAuth.find(from_id) != gChatObjectAuth.end()) { @@ -3820,29 +3823,22 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) } // truth table: - // LINDEN BUSY MUTED OWNED_BY_YOU TASK DISPLAY STORE IN HISTORY - // F F F F * Yes Yes - // F F F T * Yes Yes - // F F T F * No No - // F F T T * No No - // F T F F * No Yes - // F T F T * Yes Yes - // F T T F * No No - // F T T T * No No + // LINDEN MUTED BUSY OWNED_BY_YOU TASK DISPLAY STORE IN HISTORY + // F T * * * No No + // F F T F * No Yes + // * F F * * Yes Yes + // * F * T * Yes Yes // T * * * F Yes Yes chat.mMuted = is_muted && !is_linden; - - if (!visible_in_chat_bubble - && (is_linden || !is_busy || is_owned_by_me)) + if (!chat.mMuted) { - // show on screen and add to history - check_translate_chat(mesg, chat, FALSE); - } - else - { - // just add to chat history - check_translate_chat(mesg, chat, TRUE); + bool only_history = visible_in_chat_bubble || (!is_linden && !is_owned_by_me && is_busy); +#if 0 // Google translate doesn't work anymore + check_translate_chat(mesg, chat, only_history); +#else + add_floater_chat(chat, only_history); +#endif } } } diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h index d1ee94a3e..fea65a635 100644 --- a/indra/newview/llviewerobject.h +++ b/indra/newview/llviewerobject.h @@ -676,8 +676,9 @@ protected: TPACKETID mLatestRecvPacketID; // Latest time stamp on message from simulator // extra data sent from the sim...currently only used for tree species info U8* mData; - +public://Jay: IDGAF LLPointer mPartSourcep; // Particle source associated with this object. +protected: LLAudioSourceVO* mAudioSourcep; F32 mAudioGain; diff --git a/indra/newview/llviewerobjectlist.cpp b/indra/newview/llviewerobjectlist.cpp index 3236c4717..2bcee1478 100644 --- a/indra/newview/llviewerobjectlist.cpp +++ b/indra/newview/llviewerobjectlist.cpp @@ -80,6 +80,7 @@ #include "object_flags.h" #include "llappviewer.h" +#include "llfloaterblacklist.h" #include "llviewerobjectbackup.h" @@ -528,6 +529,15 @@ void LLViewerObjectList::processObjectUpdate(LLMessageSystem *mesgsys, } #endif + + if(std::find(LLFloaterBlacklist::blacklist_objects.begin(), + LLFloaterBlacklist::blacklist_objects.end(),fullid) != LLFloaterBlacklist::blacklist_objects.end()) + { + llinfos << "Blacklisted object asset " << fullid.asString() << " blocked." << llendl; + continue; + } + + objectp = createObject(pcode, regionp, fullid, local_id, gMessageSystem->getSender()); if (!objectp) { diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index 4b3660daf..48fb69023 100644 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -34,7 +34,10 @@ #include "llviewerregion.h" +// linden libraries #include "indra_constants.h" +#include "llavatarnamecache.h" // name lookup cap url +//#include "llfloaterreg.h" #include "llmath.h" #include "llhttpclient.h" #include "llregionflags.h" @@ -49,6 +52,8 @@ #include "llagentcamera.h" #include "llcallingcard.h" #include "llcaphttpsender.h" +#include "llcapabilitylistener.h" +#include "llcommandhandler.h" #include "lldir.h" #include "lleventpoll.h" #include "llfloatergodtools.h" @@ -62,6 +67,7 @@ #include "llurldispatcher.h" #include "llviewerobjectlist.h" #include "llviewerparceloverlay.h" +#include "llviewerstatsrecorder.h" #include "llvlmanager.h" #include "llvlcomposition.h" #include "llvocache.h" @@ -71,9 +77,6 @@ #include "stringize.h" #include "llviewercontrol.h" #include "llsdserialize.h" -#include "llviewerparcelmgr.h" - -extern BOOL gNoRender; #ifdef LL_WINDOWS #pragma warning(disable:4355) @@ -81,6 +84,14 @@ extern BOOL gNoRender; const F32 WATER_TEXTURE_SCALE = 8.f; // Number of times to repeat the water texture across a region const S16 MAX_MAP_DIST = 10; +// 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; +const F32 CAP_REQUEST_TIMEOUT = 18; +// Even though we gave up on login, keep trying for caps after we are logged in: +const S32 MAX_CAP_REQUEST_ATTEMPTS = 30; + typedef std::map CapabilityMap; class LLViewerRegionImpl { @@ -88,7 +99,11 @@ public: LLViewerRegionImpl(LLViewerRegion * region, LLHost const & host) : mHost(host), mCompositionp(NULL), - mEventPoll(NULL)//, + mEventPoll(NULL), + mSeedCapMaxAttempts(MAX_CAP_REQUEST_ATTEMPTS), + mSeedCapMaxAttemptsBeforeLogin(MAX_SEED_CAP_ATTEMPTS_BEFORE_LOGIN), + mSeedCapAttempts(0), + mHttpResponderID(0), // I'd prefer to set the LLCapabilityListener name to match the region // name -- it's disappointing that's not available at construction time. // We could instead store an LLCapabilityListener*, making @@ -98,8 +113,8 @@ public: // For testability -- the new Michael Feathers paradigm -- // LLCapabilityListener binds all the globals it expects to need at // construction time. - //mCapabilityListener(host.getString(), gMessageSystem, *region, - //gAgent.getID(), gAgent.getSessionID()) + mCapabilityListener(host.getString(), gMessageSystem, *region, + gAgent.getID(), gAgent.getSessionID()) { } @@ -137,85 +152,125 @@ public: LLEventPoll* mEventPoll; + S32 mSeedCapMaxAttempts; + S32 mSeedCapMaxAttemptsBeforeLogin; + S32 mSeedCapAttempts; + + S32 mHttpResponderID; + /// Post an event to this LLCapabilityListener to invoke a capability message on /// this LLViewerRegion's server /// (https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes#Capabilities) - //LLCapabilityListener mCapabilityListener; + LLCapabilityListener mCapabilityListener; //spatial partitions for objects in this region std::vector mObjectPartition; - - LLHTTPClient::ResponderPtr mHttpResponderPtr ; }; +// 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 : public LLCommandHandler +{ +public: + // requests will be throttled from a non-trusted browser + LLRegionHandler() : LLCommandHandler("region", /*V3: UNTRUSTED_THROTTLE*/ true) {} + + bool handle(const LLSD& params, const LLSD& query_map, LLMediaCtrl* web) + { + // 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, /*V3: "clicked",*/ web, true); + return true; + } +}; +LLRegionHandler gRegionHandler; + class BaseCapabilitiesComplete : public LLHTTPClient::Responder { LOG_CLASS(BaseCapabilitiesComplete); public: - BaseCapabilitiesComplete(LLViewerRegion* region) - : mRegion(region) + BaseCapabilitiesComplete(U64 region_handle, S32 id) + : mRegionHandle(region_handle), mID(id) { } virtual ~BaseCapabilitiesComplete() - { - if(mRegion) - { - mRegion->setHttpResponderPtrNULL() ; - } - } - - void setRegion(LLViewerRegion* region) - { - mRegion = region ; - } + { } void error(U32 statusNum, const std::string& reason) { LL_WARNS2("AppInit", "Capabilities") << statusNum << ": " << reason << LL_ENDL; - - if (STATE_SEED_GRANTED_WAIT == LLStartUp::getStartupState()) + LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(mRegionHandle); + if (regionp) { - LLStartUp::setStartupState( STATE_SEED_CAP_GRANTED ); + regionp->failedSeedCapability(); } } void result(const LLSD& content) { - if(!mRegion || LLHTTPClient::ResponderPtr(this) != mRegion->getHttpResponderPtr()) //region is removed or responder is not created. + LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(mRegionHandle); + if(!regionp) //region was removed { + LL_WARNS2("AppInit", "Capabilities") << "Received results for region that no longer exists!" << LL_ENDL; + return ; + } + if( mID != regionp->getHttpResponderID() ) // region is no longer referring to this responder + { + LL_WARNS2("AppInit", "Capabilities") << "Received results for a stale http responder!" << LL_ENDL; return ; } LLSD::map_const_iterator iter; for(iter = content.beginMap(); iter != content.endMap(); ++iter) { - mRegion->setCapability(iter->first, iter->second); + regionp->setCapability(iter->first, iter->second); LL_DEBUGS2("AppInit", "Capabilities") << "got capability for " << iter->first << LL_ENDL; /* HACK we're waiting for the ServerReleaseNotes */ - if (iter->first == "ServerReleaseNotes" && mRegion->getReleaseNotesRequested()) + if (iter->first == "ServerReleaseNotes" && regionp->getReleaseNotesRequested()) { - mRegion->showReleaseNotes(); + regionp->showReleaseNotes(); } } - mRegion->setCapabilitiesReceived(true); - + regionp->setCapabilitiesReceived(true); + if (STATE_SEED_GRANTED_WAIT == LLStartUp::getStartupState()) { LLStartUp::setStartupState( STATE_SEED_CAP_GRANTED ); } } - static boost::intrusive_ptr build( - LLViewerRegion* region) + static boost::intrusive_ptr build( U64 region_handle, S32 id ) { - return boost::intrusive_ptr( - new BaseCapabilitiesComplete(region)); + return boost::intrusive_ptr( + new BaseCapabilitiesComplete(region_handle, id) ); } private: - LLViewerRegion* mRegion; + U64 mRegionHandle; + S32 mID; }; @@ -251,31 +306,21 @@ LLViewerRegion::LLViewerRegion(const U64 &handle, mImpl->mLandp = new LLSurface('l', NULL); - if (!gNoRender) - { - // Create the composition layer for the surface - mImpl->mCompositionp = - new LLVLComposition(mImpl->mLandp, - grids_per_region_edge, - region_width_meters / grids_per_region_edge); - mImpl->mCompositionp->setSurface(mImpl->mLandp); + // Create the composition layer for the surface + mImpl->mCompositionp = + new LLVLComposition(mImpl->mLandp, + grids_per_region_edge, + region_width_meters / 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); - } + // Create the surfaces + mImpl->mLandp->setRegion(this); + mImpl->mLandp->create(grids_per_region_edge, + grids_per_patch_edge, + mImpl->mOriginGlobal, + mWidth); - if (!gNoRender) - { - mParcelOverlay = new LLViewerParcelOverlay(this, region_width_meters); - } - else - { - mParcelOverlay = NULL; - } + mParcelOverlay = new LLViewerParcelOverlay(this, region_width_meters); setOriginGlobal(from_region_handle(handle)); calculateCenterGlobal(); @@ -317,11 +362,6 @@ void LLViewerRegion::initStats() LLViewerRegion::~LLViewerRegion() { - if(mImpl->mHttpResponderPtr) - { - (static_cast(mImpl->mHttpResponderPtr.get()))->setRegion(NULL) ; - } - gVLManager.cleanupData(this); // Can't do this on destruction, because the neighbor pointers might be invalid. // This should be reference counted... @@ -345,10 +385,10 @@ LLViewerRegion::~LLViewerRegion() mImpl = NULL; } -/*LLEventPump& LLViewerRegion::getCapAPI() const +LLEventPump& LLViewerRegion::getCapAPI() const { return mImpl->mCapabilityListener.getCapAPI(); -}*/ +} /*virtual*/ const LLHost& LLViewerRegion::getHost() const @@ -537,14 +577,19 @@ 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(U32 flags) { std::string result; + if (flags & REGION_FLAGS_SANDBOX) { - if(!result.empty()) result += ", "; result += "Sandbox"; } @@ -624,6 +669,26 @@ std::string LLViewerRegion::accessToString(U8 sim_access) } } +// static +std::string LLViewerRegion::getAccessIcon(U8 sim_access) +{ + 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 ""; + } +} + // static std::string LLViewerRegion::accessToShortString(U8 sim_access) { @@ -685,6 +750,7 @@ void LLViewerRegion::dirtyHeights() BOOL LLViewerRegion::idleUpdate(F32 max_update_time) { + LLMemType mt_ivr(LLMemType::MTYPE_IDLE_UPDATE_VIEWER_REGION); // did_update returns TRUE if we did at least one significant update BOOL did_update = mImpl->mLandp->idleUpdate(max_update_time); @@ -901,14 +967,9 @@ U32 LLViewerRegion::getPacketsLost() const } } -void LLViewerRegion::setHttpResponderPtrNULL() +S32 LLViewerRegion::getHttpResponderID() const { - mImpl->mHttpResponderPtr = NULL; -} - -const LLHTTPClient::ResponderPtr LLViewerRegion::getHttpResponderPtr() const -{ - return mImpl->mHttpResponderPtr; + return mImpl->mHttpResponderID; } BOOL LLViewerRegion::pointInRegionGlobal(const LLVector3d &point_global) const @@ -1560,9 +1621,11 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames) //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("Seed") == url) @@ -1578,49 +1641,101 @@ void LLViewerRegion::setSeedCapability(const std::string& url) setCapability("Seed", url); LLSD capabilityNames = LLSD::emptyArray(); - mImpl->buildCapabilityNames(capabilityNames); - llinfos << "posting to seed " << url << llendl; - mImpl->mHttpResponderPtr = BaseCapabilitiesComplete::build(this) ; - LLHTTPClient::post(url, capabilityNames, mImpl->mHttpResponderPtr); + S32 id = ++mImpl->mHttpResponderID; + LLHTTPClient::post(url, capabilityNames, + BaseCapabilitiesComplete::build(getHandle(), id), + LLSD(), CAP_REQUEST_TIMEOUT); +} + +S32 LLViewerRegion::getNumSeedCapRetries() +{ + return mImpl->mSeedCapAttempts; +} + +void LLViewerRegion::failedSeedCapability() +{ + // Should we retry asking for caps? + mImpl->mSeedCapAttempts++; + std::string url = getCapability("Seed"); + if ( url.empty() ) + { + LL_WARNS2("AppInit", "Capabilities") << "Failed to get seed capabilities, and can not determine url for retries!" << LL_ENDL; + return; + } + // After a few attempts, continue login. We will keep trying once in-world: + if ( mImpl->mSeedCapAttempts >= mImpl->mSeedCapMaxAttemptsBeforeLogin && + STATE_SEED_GRANTED_WAIT == LLStartUp::getStartupState() ) + { + LLStartUp::setStartupState( STATE_SEED_CAP_GRANTED ); + } + + if ( mImpl->mSeedCapAttempts < mImpl->mSeedCapMaxAttempts) + { + LLSD capabilityNames = LLSD::emptyArray(); + mImpl->buildCapabilityNames(capabilityNames); + + llinfos << "posting to seed " << url << " (retry " + << mImpl->mSeedCapAttempts << ")" << llendl; + + S32 id = ++mImpl->mHttpResponderID; + LLHTTPClient::post(url, capabilityNames, + BaseCapabilitiesComplete::build(getHandle(), id), + LLSD(), CAP_REQUEST_TIMEOUT); + } + else + { + // *TODO: Give a user pop-up about this error? + LL_WARNS2("AppInit", "Capabilities") << "Failed to get seed capabilities from '" << url << "' after " << mImpl->mSeedCapAttempts << " attempts. Giving up!" << LL_ENDL; + } } class SimulatorFeaturesReceived : public LLHTTPClient::Responder { LOG_CLASS(SimulatorFeaturesReceived); public: - SimulatorFeaturesReceived(LLViewerRegion* region) - : mRegion(region) + SimulatorFeaturesReceived(const std::string& retry_url, U64 region_handle, + S32 attempt = 0, S32 max_attempts = MAX_CAP_REQUEST_ATTEMPTS) + : mRetryURL(retry_url), mRegionHandle(region_handle), mAttempt(attempt), mMaxAttempts(max_attempts) { } void error(U32 statusNum, const std::string& reason) { LL_WARNS2("AppInit", "SimulatorFeatures") << statusNum << ": " << reason << LL_ENDL; + retry(); } - + void result(const LLSD& content) { - if(!mRegion) //region is removed or responder is not created. + LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(mRegionHandle); + if(!regionp) //region is removed or responder is not created. { + LL_WARNS2("AppInit", "SimulatorFeatures") << "Received results for region that no longer exists!" << LL_ENDL; return ; } - mRegion->setSimulatorFeatures(content); + regionp->setSimulatorFeatures(content); + } + +private: + void retry() + { + if (mAttempt < mMaxAttempts) + { + mAttempt++; + LL_WARNS2("AppInit", "SimulatorFeatures") << "Re-trying '" << mRetryURL << "'. Retry #" << mAttempt << LL_ENDL; + LLHTTPClient::get(mRetryURL, new SimulatorFeaturesReceived(*this), LLSD(), CAP_REQUEST_TIMEOUT); + } } - static boost::intrusive_ptr build( - LLViewerRegion* region) - { - return boost::intrusive_ptr( - new SimulatorFeaturesReceived(region)); - } - -private: - LLViewerRegion* mRegion; + std::string mRetryURL; + U64 mRegionHandle; + S32 mAttempt; + S32 mMaxAttempts; }; @@ -1639,7 +1754,7 @@ void LLViewerRegion::setCapability(const std::string& name, const std::string& u else if (name == "SimulatorFeatures") { // kick off a request for simulator features - LLHTTPClient::get(url, new SimulatorFeaturesReceived(this)); + LLHTTPClient::get(url, new SimulatorFeaturesReceived(url, getHandle()), LLSD(), CAP_REQUEST_TIMEOUT); } else { @@ -1663,6 +1778,7 @@ std::string LLViewerRegion::getCapability(const std::string& name) const { return ""; } + return iter->second; } @@ -1714,6 +1830,37 @@ LLSpatialPartition* LLViewerRegion::getSpatialPartition(U32 type) return NULL; } +// 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 U32 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& boxes) const +{ + return (mParcelOverlay != NULL) + && (mParcelOverlay->isOwnedSelf(pos) + || mParcelOverlay->isOwnedGroup(pos) + || ((mRegionFlags & ALLOW_RETURN_ENCROACHING_OBJECT) + && mParcelOverlay->encroachesOwned(boxes)) ); +} + +bool LLViewerRegion::childrenObjectReturnable( const std::vector& boxes ) const +{ + bool result = false; + result = ( mParcelOverlay && mParcelOverlay->encroachesOnUnowned( boxes ) ) ? 1 : 0; + return result; +} + +bool LLViewerRegion::objectsCrossParcel(const std::vector& boxes) const +{ + return mParcelOverlay && mParcelOverlay->encroachesOnNearbyParcel(boxes); +} + +void LLViewerRegion::getNeighboringRegions( std::vector& uniqueRegions ) +{ + mImpl->mLandp->getNeighboringRegions( uniqueRegions ); +} + void LLViewerRegion::showReleaseNotes() { std::string url = this->getCapability("ServerReleaseNotes"); diff --git a/indra/newview/llviewerregion.h b/indra/newview/llviewerregion.h index 24e5f9052..3d33e9b7f 100644 --- a/indra/newview/llviewerregion.h +++ b/indra/newview/llviewerregion.h @@ -67,7 +67,6 @@ class LLVOCache; class LLVOCacheEntry; class LLSpatialPartition; class LLEventPump; -//class LLCapabilityListener; class LLDataPacker; class LLDataPackerBinaryBuffer; class LLHost; @@ -199,7 +198,7 @@ public: S32 getSimCPURatio() const { return mCPURatio; } const std::string& getSimColoName() const { return mColoName; } const std::string& getSimProductSKU() const { return mProductSKU; } - const std::string& getSimProductName() const { return mProductName; } + std::string getLocalizedSimProductName() const; // Returns "Sandbox", "Expensive", etc. static std::string regionFlagsToString(U32 flags); @@ -209,7 +208,10 @@ public: // Returns "M", "PG", "A" etc. static std::string accessToShortString(U8 sim_access); - + + // Return access icon name + static std::string getAccessIcon(U8 sim_access); + // helper function which just makes sure all interested parties // can process the message. static void processRegionInfo(LLMessageSystem* msg, void**); @@ -230,11 +232,12 @@ public: U32 getPacketsLost() const; - void setHttpResponderPtrNULL(); - const LLHTTPClient::ResponderPtr getHttpResponderPtr() const; + S32 getHttpResponderID() const; // Get/set named capability URLs for this region. void setSeedCapability(const std::string& url); + void failedSeedCapability(); + S32 getNumSeedCapRetries(); void setCapability(const std::string& name, const std::string& url); // implements LLCapabilityProvider virtual std::string getCapability(const std::string& name) const; @@ -249,7 +252,7 @@ public: /// Get LLEventPump on which we listen for capability requests /// (https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes#Capabilities) - // LLEventPump& getCapAPI() const; + LLEventPump& getCapAPI() const; /// implements LLCapabilityProvider /*virtual*/ const LLHost& getHost() const; @@ -328,6 +331,11 @@ public: LLSpatialPartition* getSpatialPartition(U32 type); bool objectIsReturnable(const LLVector3& pos, const std::vector& boxes) const; + bool childrenObjectReturnable( const std::vector& boxes ) const; + bool objectsCrossParcel(const std::vector& boxes) const; + + void getNeighboringRegions( std::vector& uniqueRegions ); + public: struct CompareDistance { diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp index 3fd56f842..b6fb9f397 100644 --- a/indra/newview/llviewerstats.cpp +++ b/indra/newview/llviewerstats.cpp @@ -213,6 +213,7 @@ LLViewerStats::LLViewerStats() : mObjectKBitStat("objectkbitstat"), mAssetKBitStat("assetkbitstat"), mTextureKBitStat("texturekbitstat"), + mMallocStat("mallocstat"), mVFSPendingOperations("vfspendingoperations"), mObjectsDrawnStat("objectsdrawnstat"), mObjectsCulledStat("objectsculledstat"), diff --git a/indra/newview/llviewerstats.h b/indra/newview/llviewerstats.h index 7f0f2d8cf..ec91b7efd 100644 --- a/indra/newview/llviewerstats.h +++ b/indra/newview/llviewerstats.h @@ -59,6 +59,7 @@ public: LLStat mActualInKBitStat; // From the packet ring (when faking a bad connection) LLStat mActualOutKBitStat; // From the packet ring (when faking a bad connection) LLStat mTrianglesDrawnStat; + LLStat mMallocStat; // Simulator stats LLStat mSimTimeDilation; diff --git a/indra/newview/llviewerstatsrecorder.cpp b/indra/newview/llviewerstatsrecorder.cpp new file mode 100644 index 000000000..e9d21b484 --- /dev/null +++ b/indra/newview/llviewerstatsrecorder.cpp @@ -0,0 +1,258 @@ +/** + * @file llviewerstatsrecorder.cpp + * @brief record info about viewer events to a metrics log file + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, 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 "llviewerstatsrecorder.h" + +#if LL_RECORD_VIEWER_STATS + +#include "llfile.h" +#include "llviewerregion.h" +#include "llviewerobject.h" + + +// To do - something using region name or global position +#if LL_WINDOWS + static const std::string STATS_FILE_NAME("C:\\ViewerObjectCacheStats.csv"); +#else + static const std::string STATS_FILE_NAME("/tmp/viewerstats.csv"); +#endif + +LLViewerStatsRecorder* LLViewerStatsRecorder::sInstance = NULL; +LLViewerStatsRecorder::LLViewerStatsRecorder() : + mObjectCacheFile(NULL), + mTimer(), + mRegionp(NULL), + mStartTime(0.f), + mProcessingTime(0.f) +{ + if (NULL != sInstance) + { + llerrs << "Attempted to create multiple instances of LLViewerStatsRecorder!" << llendl; + } + sInstance = this; + clearStats(); +} + +LLViewerStatsRecorder::~LLViewerStatsRecorder() +{ + if (mObjectCacheFile != NULL) + { + LLFile::close(mObjectCacheFile); + mObjectCacheFile = NULL; + } +} + +// static +void LLViewerStatsRecorder::initClass() +{ + sInstance = new LLViewerStatsRecorder(); +} + +// static +void LLViewerStatsRecorder::cleanupClass() +{ + delete sInstance; + sInstance = NULL; +} + + +void LLViewerStatsRecorder::initStatsRecorder(LLViewerRegion *regionp) +{ + if (mObjectCacheFile == NULL) + { + mStartTime = LLTimer::getTotalTime(); + mObjectCacheFile = LLFile::fopen(STATS_FILE_NAME, "wb"); + if (mObjectCacheFile) + { // Write column headers + std::ostringstream data_msg; + data_msg << "EventTime, " + << "ProcessingTime, " + << "CacheHits, " + << "CacheFullMisses, " + << "CacheCrcMisses, " + << "FullUpdates, " + << "TerseUpdates, " + << "CacheMissRequests, " + << "CacheMissResponses, " + << "CacheUpdateDupes, " + << "CacheUpdateChanges, " + << "CacheUpdateAdds, " + << "CacheUpdateReplacements, " + << "UpdateFailures" + << "\n"; + + fwrite(data_msg.str().c_str(), 1, data_msg.str().size(), mObjectCacheFile ); + } + } +} + +void LLViewerStatsRecorder::beginObjectUpdateEvents(LLViewerRegion *regionp) +{ + initStatsRecorder(regionp); + mRegionp = regionp; + mProcessingTime = LLTimer::getTotalTime(); + clearStats(); +} + +void LLViewerStatsRecorder::clearStats() +{ + mObjectCacheHitCount = 0; + mObjectCacheMissFullCount = 0; + mObjectCacheMissCrcCount = 0; + mObjectFullUpdates = 0; + mObjectTerseUpdates = 0; + mObjectCacheMissRequests = 0; + mObjectCacheMissResponses = 0; + mObjectCacheUpdateDupes = 0; + mObjectCacheUpdateChanges = 0; + mObjectCacheUpdateAdds = 0; + mObjectCacheUpdateReplacements = 0; + mObjectUpdateFailures = 0; +} + + +void LLViewerStatsRecorder::recordObjectUpdateFailure(U32 local_id, const EObjectUpdateType update_type) +{ + mObjectUpdateFailures++; +} + +void LLViewerStatsRecorder::recordCacheMissEvent(U32 local_id, const EObjectUpdateType update_type, U8 cache_miss_type) +{ + if (LLViewerRegion::CACHE_MISS_TYPE_FULL == cache_miss_type) + { + mObjectCacheMissFullCount++; + } + else + { + mObjectCacheMissCrcCount++; + } +} + +void LLViewerStatsRecorder::recordObjectUpdateEvent(U32 local_id, const EObjectUpdateType update_type, LLViewerObject * objectp) +{ + switch (update_type) + { + case OUT_FULL: + mObjectFullUpdates++; + break; + case OUT_TERSE_IMPROVED: + mObjectTerseUpdates++; + break; + case OUT_FULL_COMPRESSED: + mObjectCacheMissResponses++; + break; + case OUT_FULL_CACHED: + mObjectCacheHitCount++; + break; + default: + llwarns << "Unknown update_type" << llendl; + break; + }; +} + +void LLViewerStatsRecorder::recordCacheFullUpdate(U32 local_id, const EObjectUpdateType update_type, LLViewerRegion::eCacheUpdateResult update_result, LLViewerObject* objectp) +{ + switch (update_result) + { + case LLViewerRegion::CACHE_UPDATE_DUPE: + mObjectCacheUpdateDupes++; + break; + case LLViewerRegion::CACHE_UPDATE_CHANGED: + mObjectCacheUpdateChanges++; + break; + case LLViewerRegion::CACHE_UPDATE_ADDED: + mObjectCacheUpdateAdds++; + break; + case LLViewerRegion::CACHE_UPDATE_REPLACED: + mObjectCacheUpdateReplacements++; + break; + default: + llwarns << "Unknown update_result type" << llendl; + break; + }; +} + +void LLViewerStatsRecorder::recordRequestCacheMissesEvent(S32 count) +{ + mObjectCacheMissRequests += count; +} + +void LLViewerStatsRecorder::endObjectUpdateEvents() +{ + llinfos << "ILX: " + << mObjectCacheHitCount << " hits, " + << mObjectCacheMissFullCount << " full misses, " + << mObjectCacheMissCrcCount << " crc misses, " + << mObjectFullUpdates << " full updates, " + << mObjectTerseUpdates << " terse updates, " + << mObjectCacheMissRequests << " cache miss requests, " + << mObjectCacheMissResponses << " cache miss responses, " + << mObjectCacheUpdateDupes << " cache update dupes, " + << mObjectCacheUpdateChanges << " cache update changes, " + << mObjectCacheUpdateAdds << " cache update adds, " + << mObjectCacheUpdateReplacements << " cache update replacements, " + << mObjectUpdateFailures << " update failures" + << llendl; + + S32 total_objects = mObjectCacheHitCount + mObjectCacheMissCrcCount + mObjectCacheMissFullCount + mObjectFullUpdates + mObjectTerseUpdates + mObjectCacheMissRequests + mObjectCacheMissResponses + mObjectCacheUpdateDupes + mObjectCacheUpdateChanges + mObjectCacheUpdateAdds + mObjectCacheUpdateReplacements + mObjectUpdateFailures; + if (mObjectCacheFile != NULL && + total_objects > 0) + { + std::ostringstream data_msg; + F32 processing32 = (F32) ((LLTimer::getTotalTime() - mProcessingTime) / 1000.0); + + data_msg << getTimeSinceStart() + << ", " << processing32 + << ", " << mObjectCacheHitCount + << ", " << mObjectCacheMissFullCount + << ", " << mObjectCacheMissCrcCount + << ", " << mObjectFullUpdates + << ", " << mObjectTerseUpdates + << ", " << mObjectCacheMissRequests + << ", " << mObjectCacheMissResponses + << ", " << mObjectCacheUpdateDupes + << ", " << mObjectCacheUpdateChanges + << ", " << mObjectCacheUpdateAdds + << ", " << mObjectCacheUpdateReplacements + << ", " << mObjectUpdateFailures + << "\n"; + + fwrite(data_msg.str().c_str(), 1, data_msg.str().size(), mObjectCacheFile ); + } + + clearStats(); +} + +F32 LLViewerStatsRecorder::getTimeSinceStart() +{ + return (F32) ((LLTimer::getTotalTime() - mStartTime) / 1000.0); +} + +#endif + + + diff --git a/indra/newview/llviewerstatsrecorder.h b/indra/newview/llviewerstatsrecorder.h new file mode 100644 index 000000000..612ac380f --- /dev/null +++ b/indra/newview/llviewerstatsrecorder.h @@ -0,0 +1,97 @@ +/** + * @file llviewerstatsrecorder.h + * @brief record info about viewer events to a metrics log file + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, 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$ + */ + +#ifndef LLVIEWERSTATSRECORDER_H +#define LLVIEWERSTATSRECORDER_H + + +// This is a diagnostic class used to record information from the viewer +// for analysis. + +// This is normally 0. Set to 1 to enable viewer stats recording +#define LL_RECORD_VIEWER_STATS 0 + + +#if LL_RECORD_VIEWER_STATS +#include "llframetimer.h" +#include "llviewerobject.h" +#include "llviewerregion.h" + +class LLMutex; +class LLViewerRegion; +class LLViewerObject; + +class LLViewerStatsRecorder +{ + public: + LLViewerStatsRecorder(); + ~LLViewerStatsRecorder(); + + static void initClass(); + static void cleanupClass(); + static LLViewerStatsRecorder* instance() {return sInstance; } + + void initStatsRecorder(LLViewerRegion *regionp); + + void beginObjectUpdateEvents(LLViewerRegion *regionp); + void recordObjectUpdateFailure(U32 local_id, const EObjectUpdateType update_type); + void recordCacheMissEvent(U32 local_id, const EObjectUpdateType update_type, U8 cache_miss_type); + void recordObjectUpdateEvent(U32 local_id, const EObjectUpdateType update_type, LLViewerObject * objectp); + void recordCacheFullUpdate(U32 local_id, const EObjectUpdateType update_type, LLViewerRegion::eCacheUpdateResult update_result, LLViewerObject* objectp); + void recordRequestCacheMissesEvent(S32 count); + void endObjectUpdateEvents(); + + F32 getTimeSinceStart(); + +private: + static LLViewerStatsRecorder* sInstance; + + LLFILE * mObjectCacheFile; // File to write data into + LLFrameTimer mTimer; + LLViewerRegion* mRegionp; + F64 mStartTime; + F64 mProcessingTime; + + S32 mObjectCacheHitCount; + S32 mObjectCacheMissFullCount; + S32 mObjectCacheMissCrcCount; + S32 mObjectFullUpdates; + S32 mObjectTerseUpdates; + S32 mObjectCacheMissRequests; + S32 mObjectCacheMissResponses; + S32 mObjectCacheUpdateDupes; + S32 mObjectCacheUpdateChanges; + S32 mObjectCacheUpdateAdds; + S32 mObjectCacheUpdateReplacements; + S32 mObjectUpdateFailures; + + + void clearStats(); +}; +#endif // LL_RECORD_VIEWER_STATS + +#endif // LLVIEWERSTATSRECORDER_H + diff --git a/indra/newview/llviewerthrottle.cpp b/indra/newview/llviewerthrottle.cpp index bf779c427..73065c5c0 100644 --- a/indra/newview/llviewerthrottle.cpp +++ b/indra/newview/llviewerthrottle.cpp @@ -40,6 +40,8 @@ #include "llviewerstats.h" #include "lldatapacker.h" +using namespace LLOldEvents; + // consts // The viewer is allowed to set the under-the-hood bandwidth to 50% diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 1c93eefae..f06fce3e4 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -2771,8 +2771,12 @@ void LLViewerWindow::handleScrollWheel(S32 clicks) void LLViewerWindow::moveCursorToCenter() { - S32 x = getWorldViewWidthScaled() / 2; - S32 y = getWorldViewHeightScaled() / 2; + if (gSavedSettings.getBOOL("SGAbsolutePointer")) { + return; + } + + S32 x = getWorldViewWidthScaled() / 2; + S32 y = getWorldViewHeightScaled() / 2; //on a forced move, all deltas get zeroed out to prevent jumping mCurrentMousePoint.set(x,y); diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index 83197f3d8..7f718bfef 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -979,6 +979,9 @@ private: LLFrameTimer mTimeVisible; std::deque mChats; BOOL mTyping; +public: + BOOL isTyping(){ return mTyping; } +private: LLFrameTimer mTypingTimer; static void on_avatar_name_response(const LLUUID& agent_id, const LLAvatarName& av_name, void *userdata); diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index 7e4916293..df0925c6c 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -3692,10 +3692,10 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) else { if (te->getColor().mV[3] > 0.f) - { + { //only treat as alpha in the pipeline if < 100% transparent drawablep->setState(LLDrawable::HAS_ALPHA); - alpha_faces.push_back(facep); } + alpha_faces.push_back(facep); } } else @@ -4223,7 +4223,11 @@ void LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, std:: if (is_alpha) { // can we safely treat this as an alpha mask? - if (facep->canRenderAsMask()) + if (facep->getFaceColor().mV[3] <= 0.f) + { //100% transparent, don't render unless we're highlighting transparent + registerFace(group, facep, LLRenderPass::PASS_ALPHA_INVISIBLE); + } + else if (facep->canRenderAsMask()) { if (te->getFullbright() || LLPipeline::sNoAlpha) { diff --git a/indra/newview/llworldmapview.cpp b/indra/newview/llworldmapview.cpp index c88b58389..f8269833b 100644 --- a/indra/newview/llworldmapview.cpp +++ b/indra/newview/llworldmapview.cpp @@ -773,10 +773,12 @@ void LLWorldMapView::drawTiles(S32 width, S32 height) { // 1. Tiles are zoomed out small enough, or // 2. Sim's texture has not been loaded yet F32 map_scale_cutoff = SIM_MAP_SCALE; +#if 0 // This is always false, as REGION_FLAGS_NULL_LAYER doesn't exist anymore if ((info->getRegionFlags() & REGION_FLAGS_NULL_LAYER) > 0) { map_scale_cutoff = SIM_NULL_MAP_SCALE; } +#endif bool sim_visible = (sMapScale >= map_scale_cutoff) && @@ -910,7 +912,7 @@ void LLWorldMapView::drawTiles(S32 width, S32 height) { gGL.end(); } - if ((info->getRegionFlags() & REGION_FLAGS_NULL_LAYER) == 0) + if (true /*V3: REGION_FLAGS_NULL_LAYER doesn't exist... (info->getRegionFlags() & REGION_FLAGS_NULL_LAYER) == 0*/) { // draw an alpha of 1 where the sims are visible (except NULL sims) gGL.flush(); diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index 2d4fe26b5..3c113c0ac 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -3105,6 +3105,8 @@ void renderSoundHighlights(LLDrawable* drawablep) } } +void updateParticleActivity(LLDrawable *drawablep); + void LLPipeline::postSort(LLCamera& camera) { LLMemType mt(LLMemType::MTYPE_PIPELINE_POST_SORT); @@ -3235,6 +3237,9 @@ void LLPipeline::postSort(LLCamera& camera) std::sort(sCull->beginAlphaGroups(), sCull->endAlphaGroups(), LLSpatialGroup::CompareDepthGreater()); } llpushcallstacks ; + + forAllVisibleDrawables(updateParticleActivity); + // only render if the flag is set. The flag is only set if we are in edit mode or the toggle is set in the menus static const LLCachedControl beacons_visible("BeaconsVisible", false); if (beacons_visible && !sShadowRender) diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h index 47e7b963e..ac13a200a 100644 --- a/indra/newview/pipeline.h +++ b/indra/newview/pipeline.h @@ -45,6 +45,7 @@ #include "llgl.h" #include "lldrawable.h" #include "llrendertarget.h" +#include "llfasttimer.h" #include @@ -368,7 +369,7 @@ private: void removeFromQuickLookup( LLDrawPool* poolp ); BOOL updateDrawableGeom(LLDrawable* drawable, BOOL priority); void assertInitializedDoError(); - bool assertInitialized() { const bool is_init = isInit(); if (!is_init) assertInitializedDoError(); return is_init; }; + bool assertInitialized() { const bool is_init = isInit(); if (!is_init) assertInitializedDoError(); return is_init; } public: enum {GPU_CLASS_MAX = 3 }; diff --git a/indra/newview/rlvcommon.cpp b/indra/newview/rlvcommon.cpp index efb70cd71..5943f4af8 100644 --- a/indra/newview/rlvcommon.cpp +++ b/indra/newview/rlvcommon.cpp @@ -20,7 +20,7 @@ #include "llavatarnamecache.h" #include "llnotificationsutil.h" #include "lluictrlfactory.h" -#include "llversionviewer.h" +#include "sgversion.h" #include "llviewermenu.h" #include "llviewerparcelmgr.h" #include "llviewerregion.h" @@ -31,6 +31,8 @@ #include "rlvcommon.h" #include "rlvhandler.h" +using namespace LLOldEvents; + // ============================================================================ // RlvNotifications // @@ -305,7 +307,7 @@ std::string RlvStrings::getVersion(bool fLegacy /*=false*/) return llformat("%s viewer v%d.%d.%d (%s %d.%d.%d.%d - RLVa %d.%d.%d)", ( (!fLegacy) ? "RestrainedLove" : "RestrainedLife" ), RLV_VERSION_MAJOR, RLV_VERSION_MINOR, RLV_VERSION_PATCH, - LLAppViewer::instance()->getSecondLifeTitle().c_str(), LL_VERSION_MAJOR, LL_VERSION_MINOR, LL_VERSION_PATCH, LL_VERSION_BUILD, + LLAppViewer::instance()->getSecondLifeTitle().c_str(), gVersionMajor, gVersionMinor, gVersionPatch, gVersionBuild, RLVa_VERSION_MAJOR, RLVa_VERSION_MINOR, RLVa_VERSION_PATCH); } diff --git a/indra/newview/rlvcommon.h b/indra/newview/rlvcommon.h index b2513a467..ac097ab5d 100644 --- a/indra/newview/rlvcommon.h +++ b/indra/newview/rlvcommon.h @@ -195,7 +195,7 @@ typedef bool (RlvCommandHandler::*rlvCommandHandler)(const RlvCommand& rlvCmd, E class RlvEnableIfNot : public LLMemberListener { - bool handleEvent(LLPointer, const LLSD&); + bool handleEvent(LLPointer, const LLSD&); }; // ============================================================================ diff --git a/indra/newview/sgmemstat.cpp b/indra/newview/sgmemstat.cpp new file mode 100644 index 000000000..27f3cc7cb --- /dev/null +++ b/indra/newview/sgmemstat.cpp @@ -0,0 +1,31 @@ +/* Copyright (C) 2012 Siana Gearz + * + * 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; either + * version 2.1 of the License, or (at your option) any later version. + * + * 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 */ + +#include "llviewerprecompiledheaders.h" +#include "sgmemstat.h" + +bool SGMemStat::haveStat() { + return false; +} + +F32 SGMemStat::getMalloc() { + return 0.f; +} + +U32 SGMemStat::getNumObjects() { + return 0; +} diff --git a/indra/newview/sgmemstat.h b/indra/newview/sgmemstat.h new file mode 100644 index 000000000..8a89e3b0d --- /dev/null +++ b/indra/newview/sgmemstat.h @@ -0,0 +1,31 @@ +/* Copyright (C) 2012 Siana Gearz + * + * 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; either + * version 2.1 of the License, or (at your option) any later version. + * + * 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 */ + +#ifndef SGMEMSTAT_H +#define SGMEMSTAT_H + +namespace SGMemStat{ + +bool haveStat(); + +F32 getMalloc(); + +U32 getNumObjects(); + +} + +#endif diff --git a/indra/newview/sgversion.cpp b/indra/newview/sgversion.cpp new file mode 100644 index 000000000..615064f27 --- /dev/null +++ b/indra/newview/sgversion.cpp @@ -0,0 +1,33 @@ +/* Copyright (C) 2012 Siana Gearz + * + * 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; either + * version 2.1 of the License, or (at your option) any later version. + * + * 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 */ + +#include "llviewerprecompiledheaders.h" + +#include "llversionviewer.h" + +#include "sgversion.h" + +const S32 gVersionMajor = LL_VERSION_MAJOR; +const S32 gVersionMinor = LL_VERSION_MINOR; +const S32 gVersionPatch = LL_VERSION_PATCH; +const S32 gVersionBuild = LL_VERSION_BUILD; + +const char* gVersionChannel = LL_CHANNEL; + +#if LL_DARWIN +const char* gVersionBundleID = LL_VERSION_BUNDLE_ID; +#endif \ No newline at end of file diff --git a/indra/newview/sgversion.h b/indra/newview/sgversion.h new file mode 100644 index 000000000..afdc8b51a --- /dev/null +++ b/indra/newview/sgversion.h @@ -0,0 +1,32 @@ +/* Copyright (C) 2012 Siana Gearz + * + * 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; either + * version 2.1 of the License, or (at your option) any later version. + * + * 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 */ + +#ifndef SGVERSION_H +#define SGVERSION_H + +extern const S32 gVersionMajor; +extern const S32 gVersionMinor; +extern const S32 gVersionPatch; +extern const S32 gVersionBuild; + +extern const char* gVersionChannel; + +#if LL_DARWIN +extern const char* gVersionBundleID; +#endif + +#endif diff --git a/indra/newview/skins/Default.xml b/indra/newview/skins/Default.xml index 67f7abce4..aa3a9dfd0 100644 --- a/indra/newview/skins/Default.xml +++ b/indra/newview/skins/Default.xml @@ -1,7 +1,7 @@ skin_name - Default + Classic author_name Linden Lab additional_author_names diff --git a/indra/newview/skins/dark/textures/preview.png b/indra/newview/skins/dark/textures/preview.png index 2cac82923..a9f2b8b02 100644 Binary files a/indra/newview/skins/dark/textures/preview.png and b/indra/newview/skins/dark/textures/preview.png differ diff --git a/indra/newview/skins/dark/textures/scrollbutton_up_out_blue.tga b/indra/newview/skins/dark/textures/scrollbutton_up_out_blue.tga new file mode 100644 index 000000000..9112d187e Binary files /dev/null and b/indra/newview/skins/dark/textures/scrollbutton_up_out_blue.tga differ diff --git a/indra/newview/skins/default/textures/Inv_WaterLight.png b/indra/newview/skins/default/textures/Inv_WaterLight.png new file mode 100644 index 000000000..a5bc5f549 Binary files /dev/null and b/indra/newview/skins/default/textures/Inv_WaterLight.png differ diff --git a/indra/newview/skins/default/textures/Inv_WindLight.png b/indra/newview/skins/default/textures/Inv_WindLight.png new file mode 100644 index 000000000..0e633a38f Binary files /dev/null and b/indra/newview/skins/default/textures/Inv_WindLight.png differ diff --git a/indra/newview/skins/default/textures/arrow_left.tga b/indra/newview/skins/default/textures/arrow_left.tga new file mode 100644 index 000000000..68e628095 Binary files /dev/null and b/indra/newview/skins/default/textures/arrow_left.tga differ diff --git a/indra/newview/skins/default/textures/arrow_right.tga b/indra/newview/skins/default/textures/arrow_right.tga new file mode 100644 index 000000000..7a5a30ec4 Binary files /dev/null and b/indra/newview/skins/default/textures/arrow_right.tga differ diff --git a/indra/newview/skins/default/textures/avatar_gone.tga b/indra/newview/skins/default/textures/avatar_gone.tga new file mode 100644 index 000000000..e5c2c070b Binary files /dev/null and b/indra/newview/skins/default/textures/avatar_gone.tga differ diff --git a/indra/newview/skins/default/textures/avatar_new.tga b/indra/newview/skins/default/textures/avatar_new.tga new file mode 100644 index 000000000..854b70c32 Binary files /dev/null and b/indra/newview/skins/default/textures/avatar_new.tga differ diff --git a/indra/newview/skins/default/textures/avatar_typing.tga b/indra/newview/skins/default/textures/avatar_typing.tga new file mode 100644 index 000000000..2c549025d Binary files /dev/null and b/indra/newview/skins/default/textures/avatar_typing.tga differ diff --git a/indra/newview/skins/default/textures/particles_scan.tga b/indra/newview/skins/default/textures/particles_scan.tga new file mode 100644 index 000000000..008943981 Binary files /dev/null and b/indra/newview/skins/default/textures/particles_scan.tga differ diff --git a/indra/newview/skins/default/xui/en-us/floater_blacklist.xml b/indra/newview/skins/default/xui/en-us/floater_blacklist.xml index ec7fbf667..9d40085b1 100644 --- a/indra/newview/skins/default/xui/en-us/floater_blacklist.xml +++ b/indra/newview/skins/default/xui/en-us/floater_blacklist.xml @@ -10,13 +10,15 @@ - + +