/** * @file llgesturemgr.cpp * @brief Manager for playing gestures on the viewer * * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. * $License$ */ #include "llviewerprecompiledheaders.h" #include "llgesturemgr.h" // system #include #include #include // library #include "lldatapacker.h" #include "llinventory.h" #include "llmultigesture.h" #include "llstl.h" #include "llstring.h" // todo: remove #include "llvfile.h" #include "message.h" // newview #include "llagent.h" #include "llchatbar.h" #include "llinventorymodel.h" #include "llnotify.h" #include "llviewermessage.h" #include "llvoavatar.h" #include "llviewerstats.h" #include "viewer.h" LLGestureManager gGestureManager; // Longest time, in seconds, to wait for all animations to stop playing const F32 MAX_WAIT_ANIM_SECS = 30.f; // Lightweight constructor. // init() does the heavy lifting. LLGestureManager::LLGestureManager() : mValid(FALSE), mPlaying(), mActive(), mLoadingCount(0) { } // We own the data for gestures, so clean them up. LLGestureManager::~LLGestureManager() { item_map_t::iterator it; for (it = mActive.begin(); it != mActive.end(); ++it) { LLMultiGesture* gesture = (*it).second; delete gesture; gesture = NULL; } } void LLGestureManager::init() { // TODO } // Use this version when you have the item_id but not the asset_id, // and you KNOW the inventory is loaded. void LLGestureManager::activateGesture(const LLUUID& item_id) { LLViewerInventoryItem* item = gInventory.getItem(item_id); if (!item) return; LLUUID asset_id = item->getAssetUUID(); mLoadingCount = 1; mDeactivateSimilarNames.clear(); const BOOL inform_server = TRUE; const BOOL deactivate_similar = TRUE; activateGestureWithAsset(item_id, asset_id, inform_server, deactivate_similar); } void LLGestureManager::activateGestures(LLViewerInventoryItem::item_array_t& items) { // Load up the assets S32 count = 0; LLViewerInventoryItem::item_array_t::const_iterator it; for (it = items.begin(); it != items.end(); ++it) { LLViewerInventoryItem* item = *it; if (isGestureActive(item->getUUID())) { continue; } else { // Make gesture active and persistent through login sessions. -spatters 07-12-06 activateGesture(item->getUUID()); } count++; } mLoadingCount = count; mDeactivateSimilarNames.clear(); for (it = items.begin(); it != items.end(); ++it) { LLViewerInventoryItem* item = *it; if (isGestureActive(item->getUUID())) { continue; } // Don't inform server, we'll do that in bulk const BOOL no_inform_server = FALSE; const BOOL deactivate_similar = TRUE; activateGestureWithAsset(item->getUUID(), item->getAssetUUID(), no_inform_server, deactivate_similar); } // Inform the database of this change LLMessageSystem* msg = gMessageSystem; BOOL start_message = TRUE; for (it = items.begin(); it != items.end(); ++it) { LLViewerInventoryItem* item = *it; if (isGestureActive(item->getUUID())) { continue; } if (start_message) { msg->newMessage("ActivateGestures"); msg->nextBlock("AgentData"); msg->addUUID("AgentID", gAgent.getID()); msg->addUUID("SessionID", gAgent.getSessionID()); msg->addU32("Flags", 0x0); start_message = FALSE; } msg->nextBlock("Data"); msg->addUUID("ItemID", item->getUUID()); msg->addUUID("AssetID", item->getAssetUUID()); msg->addU32("GestureFlags", 0x0); if (msg->getCurrentSendTotal() > MTUBYTES) { gAgent.sendReliableMessage(); start_message = TRUE; } } if (!start_message) { gAgent.sendReliableMessage(); } } struct LLLoadInfo { LLUUID mItemID; BOOL mInformServer; BOOL mDeactivateSimilar; }; // If inform_server is true, will send a message upstream to update // the user_gesture_active table. void LLGestureManager::activateGestureWithAsset(const LLUUID& item_id, const LLUUID& asset_id, BOOL inform_server, BOOL deactivate_similar) { if( !gAssetStorage ) { llwarns << "LLGestureManager::activateGestureWithAsset without valid gAssetStorage" << llendl; return; } // If gesture is already active, nothing to do. if (isGestureActive(item_id)) { llwarns << "Tried to loadGesture twice " << item_id << llendl; return; } // if (asset_id.isNull()) // { // llwarns << "loadGesture() - gesture has no asset" << llendl; // return; // } // For now, put NULL into the item map. We'll build a gesture // class object when the asset data arrives. mActive[item_id] = NULL; // Copy the UUID if (asset_id.notNull()) { LLLoadInfo* info = new LLLoadInfo; info->mItemID = item_id; info->mInformServer = inform_server; info->mDeactivateSimilar = deactivate_similar; const BOOL high_priority = TRUE; gAssetStorage->getAssetData(asset_id, LLAssetType::AT_GESTURE, onLoadComplete, (void*)info, high_priority); } else { notifyObservers(); } } void LLGestureManager::deactivateGesture(const LLUUID& item_id) { item_map_t::iterator it = mActive.find(item_id); if (it == mActive.end()) { llwarns << "deactivateGesture for inactive gesture " << item_id << llendl; return; } // mActive owns this gesture pointer, so clean up memory. LLMultiGesture* gesture = (*it).second; // Can be NULL gestures in the map if (gesture) { stopGesture(gesture); delete gesture; gesture = NULL; } mActive.erase(it); gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); // Inform the database of this change LLMessageSystem* msg = gMessageSystem; msg->newMessage("DeactivateGestures"); msg->nextBlock("AgentData"); msg->addUUID("AgentID", gAgent.getID()); msg->addUUID("SessionID", gAgent.getSessionID()); msg->addU32("Flags", 0x0); msg->nextBlock("Data"); msg->addUUID("ItemID", item_id); msg->addU32("GestureFlags", 0x0); gAgent.sendReliableMessage(); notifyObservers(); } void LLGestureManager::deactivateSimilarGestures(LLMultiGesture* in, const LLUUID& in_item_id) { std::vector gest_item_ids; // Deactivate all gestures that match item_map_t::iterator it; for (it = mActive.begin(); it != mActive.end(); ) { const LLUUID& item_id = (*it).first; LLMultiGesture* gest = (*it).second; // Don't deactivate the gesture we are looking for duplicates of // (for replaceGesture) if (!gest || item_id == in_item_id) { // legal, can have null pointers in list ++it; } else if ((!gest->mTrigger.empty() && gest->mTrigger == in->mTrigger) || (gest->mKey != KEY_NONE && gest->mKey == in->mKey && gest->mMask == in->mMask)) { gest_item_ids.push_back(item_id); stopGesture(gest); delete gest; gest = NULL; mActive.erase(it++); gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); } else { ++it; } } // Inform database of the change LLMessageSystem* msg = gMessageSystem; BOOL start_message = TRUE; std::vector::const_iterator vit = gest_item_ids.begin(); while (vit != gest_item_ids.end()) { if (start_message) { msg->newMessage("DeactivateGestures"); msg->nextBlock("AgentData"); msg->addUUID("AgentID", gAgent.getID()); msg->addUUID("SessionID", gAgent.getSessionID()); msg->addU32("Flags", 0x0); start_message = FALSE; } msg->nextBlock("Data"); msg->addUUID("ItemID", *vit); msg->addU32("GestureFlags", 0x0); if (msg->getCurrentSendTotal() > MTUBYTES) { gAgent.sendReliableMessage(); start_message = TRUE; } ++vit; } if (!start_message) { gAgent.sendReliableMessage(); } // Add to the list of names for the user. for (vit = gest_item_ids.begin(); vit != gest_item_ids.end(); ++vit) { LLViewerInventoryItem* item = gInventory.getItem(*vit); if (!item) continue; mDeactivateSimilarNames.append(item->getName()); mDeactivateSimilarNames.append("\n"); } notifyObservers(); } BOOL LLGestureManager::isGestureActive(const LLUUID& item_id) { item_map_t::iterator it = mActive.find(item_id); return (it != mActive.end()); } BOOL LLGestureManager::isGesturePlaying(const LLUUID& item_id) { item_map_t::iterator it = mActive.find(item_id); if (it == mActive.end()) return FALSE; LLMultiGesture* gesture = (*it).second; if (!gesture) return FALSE; return gesture->mPlaying; } void LLGestureManager::replaceGesture(const LLUUID& item_id, LLMultiGesture* new_gesture, const LLUUID& asset_id) { item_map_t::iterator it = mActive.find(item_id); if (it == mActive.end()) { llwarns << "replaceGesture for inactive gesture " << item_id << llendl; return; } LLMultiGesture* old_gesture = (*it).second; stopGesture(old_gesture); mActive.erase(item_id); mActive[item_id] = new_gesture; delete old_gesture; old_gesture = NULL; if (asset_id.notNull()) { mLoadingCount = 1; mDeactivateSimilarNames.clear(); LLLoadInfo* info = new LLLoadInfo; info->mItemID = item_id; info->mInformServer = TRUE; info->mDeactivateSimilar = TRUE; const BOOL high_priority = TRUE; gAssetStorage->getAssetData(asset_id, LLAssetType::AT_GESTURE, onLoadComplete, (void*)info, high_priority); } notifyObservers(); } void LLGestureManager::playGesture(LLMultiGesture* gesture) { if (!gesture) return; // Reset gesture to first step gesture->mCurrentStep = 0; // Add to list of playing gesture->mPlaying = TRUE; mPlaying.push_back(gesture); // And get it going stepGesture(gesture); notifyObservers(); } // Convenience function that looks up the item_id for you. void LLGestureManager::playGesture(const LLUUID& item_id) { item_map_t::iterator it = mActive.find(item_id); if (it == mActive.end()) return; LLMultiGesture* gesture = (*it).second; if (!gesture) return; playGesture(gesture); } // Iterates through space delimited tokens in string, triggering any gestures found. // Generates a revised string that has the found tokens replaced by their replacement strings // and (as a minor side effect) has multiple spaces in a row replaced by single spaces. BOOL LLGestureManager::triggerAndReviseString(const std::string &utf8str, std::string* revised_string) { LLString tokenized = LLString(utf8str.c_str()); BOOL found_gestures = FALSE; BOOL first_token = TRUE; typedef boost::tokenizer > tokenizer; boost::char_separator sep(" "); tokenizer tokens(tokenized, sep); tokenizer::iterator token_iter; for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) { const char* cur_token = token_iter->c_str(); LLMultiGesture* gesture = NULL; // Only pay attention to the first gesture in the string. if( !found_gestures ) { LLString cur_token_lower = cur_token; LLString::toLower(cur_token_lower); item_map_t::iterator it; for (it = mActive.begin(); it != mActive.end(); ++it) { gesture = (*it).second; // Gesture asset data might not have arrived yet if (!gesture) continue; if (!stricmp(gesture->mTrigger.c_str(), cur_token_lower.c_str())) { playGesture(gesture); if (!gesture->mReplaceText.empty()) { if( !first_token ) { revised_string->append( " " ); } // Don't muck with the user's capitalization if we don't have to. LLString output = gesture->mReplaceText.c_str(); LLString output_lower = output; LLString::toLower(output_lower); if( cur_token_lower == output_lower ) { revised_string->append( cur_token ); } else { revised_string->append( output ); } } found_gestures = TRUE; break; } gesture = NULL; } } if( !gesture ) { if( !first_token ) { revised_string->append( " " ); } revised_string->append( cur_token ); } first_token = FALSE; } return found_gestures; } BOOL LLGestureManager::triggerGesture(KEY key, MASK mask) { item_map_t::iterator it; for (it = mActive.begin(); it != mActive.end(); ++it) { LLMultiGesture* gesture = (*it).second; // asset data might not have arrived yet if (!gesture) continue; if (gesture->mKey == key && gesture->mMask == mask) { playGesture(gesture); return TRUE; } } return FALSE; } S32 LLGestureManager::getPlayingCount() const { return mPlaying.size(); } struct IsGesturePlaying : public std::unary_function { bool operator()(const LLMultiGesture* gesture) const { return gesture->mPlaying ? true : false; } }; void LLGestureManager::update() { S32 i; for (i = 0; i < (S32)mPlaying.size(); ++i) { stepGesture(mPlaying[i]); } // Clear out gestures that are done, by moving all the // ones that are still playing to the front. std::vector::iterator new_end; new_end = std::partition(mPlaying.begin(), mPlaying.end(), IsGesturePlaying()); // Something finished playing if (new_end != mPlaying.end()) { // Delete the completed gestures that want deletion std::vector::iterator it; for (it = new_end; it != mPlaying.end(); ++it) { LLMultiGesture* gesture = *it; if (gesture->mDoneCallback) { gesture->mDoneCallback(gesture, gesture->mCallbackData); // callback might have deleted gesture, can't // rely on this pointer any more gesture = NULL; } } // And take done gestures out of the playing list mPlaying.erase(new_end, mPlaying.end()); notifyObservers(); } } // Run all steps until you're either done or hit a wait. void LLGestureManager::stepGesture(LLMultiGesture* gesture) { if (!gesture) { return; } LLVOAvatar* avatar = gAgent.getAvatarObject(); if (!avatar) return; // Of the ones that started playing, have any stopped? std::set::iterator gest_it; for (gest_it = gesture->mPlayingAnimIDs.begin(); gest_it != gesture->mPlayingAnimIDs.end(); ) { // look in signaled animations (simulator's view of what is // currently playing. LLVOAvatar::AnimIterator play_it = avatar->mSignaledAnimations.find(*gest_it); if (play_it != avatar->mSignaledAnimations.end()) { ++gest_it; } else { // not found, so not currently playing or scheduled to play // delete from the triggered set gesture->mPlayingAnimIDs.erase(gest_it++); } } // Of all the animations that we asked the sim to start for us, // pick up the ones that have actually started. for (gest_it = gesture->mRequestedAnimIDs.begin(); gest_it != gesture->mRequestedAnimIDs.end(); ) { LLVOAvatar::AnimIterator play_it = avatar->mSignaledAnimations.find(*gest_it); if (play_it != avatar->mSignaledAnimations.end()) { // Hooray, this animation has started playing! // Copy into playing. gesture->mPlayingAnimIDs.insert(*gest_it); gesture->mRequestedAnimIDs.erase(gest_it++); } else { // nope, not playing yet ++gest_it; } } // Run the current steps BOOL waiting = FALSE; while (!waiting && gesture->mPlaying) { // Get the current step, if there is one. // Otherwise enter the waiting at end state. LLGestureStep* step = NULL; if (gesture->mCurrentStep < (S32)gesture->mSteps.size()) { step = gesture->mSteps[gesture->mCurrentStep]; llassert(step != NULL); } else { // step stays null, we're off the end gesture->mWaitingAtEnd = TRUE; } // If we're waiting at the end, wait for all gestures to stop // playing. // TODO: Wait for all sounds to complete as well. if (gesture->mWaitingAtEnd) { // Neither do we have any pending requests, nor are they // still playing. if ((gesture->mRequestedAnimIDs.empty() && gesture->mPlayingAnimIDs.empty())) { // all animations are done playing gesture->mWaitingAtEnd = FALSE; gesture->mPlaying = FALSE; } else { waiting = TRUE; } continue; } // If we're waiting on our animations to stop, poll for // completion. if (gesture->mWaitingAnimations) { // Neither do we have any pending requests, nor are they // still playing. if ((gesture->mRequestedAnimIDs.empty() && gesture->mPlayingAnimIDs.empty())) { // all animations are done playing gesture->mWaitingAnimations = FALSE; gesture->mCurrentStep++; } else if (gesture->mWaitTimer.getElapsedTimeF32() > MAX_WAIT_ANIM_SECS) { // we've waited too long for an animation llinfos << "Waited too long for animations to stop, continuing gesture." << llendl; gesture->mWaitingAnimations = FALSE; gesture->mCurrentStep++; } else { waiting = TRUE; } continue; } // If we're waiting a fixed amount of time, check for timer // expiration. if (gesture->mWaitingTimer) { // We're waiting for a certain amount of time to pass LLGestureStepWait* wait_step = (LLGestureStepWait*)step; F32 elapsed = gesture->mWaitTimer.getElapsedTimeF32(); if (elapsed > wait_step->mWaitSeconds) { // wait is done, continue execution gesture->mWaitingTimer = FALSE; gesture->mCurrentStep++; } else { // we're waiting, so execution is done for now waiting = TRUE; } continue; } // Not waiting, do normal execution runStep(gesture, step); } } void LLGestureManager::runStep(LLMultiGesture* gesture, LLGestureStep* step) { switch(step->getType()) { case STEP_ANIMATION: { LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step; if (anim_step->mAnimAssetID.isNull()) { gesture->mCurrentStep++; } if (anim_step->mFlags & ANIM_FLAG_STOP) { gAgent.sendAnimationRequest(anim_step->mAnimAssetID, ANIM_REQUEST_STOP); // remove it from our request set in case we just requested it std::set::iterator set_it = gesture->mRequestedAnimIDs.find(anim_step->mAnimAssetID); if (set_it != gesture->mRequestedAnimIDs.end()) { gesture->mRequestedAnimIDs.erase(set_it); } } else { gAgent.sendAnimationRequest(anim_step->mAnimAssetID, ANIM_REQUEST_START); // Indicate that we've requested this animation to play as // part of this gesture (but it won't start playing for at // least one round-trip to simulator). gesture->mRequestedAnimIDs.insert(anim_step->mAnimAssetID); } gesture->mCurrentStep++; break; } case STEP_SOUND: { LLGestureStepSound* sound_step = (LLGestureStepSound*)step; const LLUUID& sound_id = sound_step->mSoundAssetID; const F32 volume = 1.f; send_sound_trigger(sound_id, volume); gesture->mCurrentStep++; break; } case STEP_CHAT: { LLGestureStepChat* chat_step = (LLGestureStepChat*)step; std::string chat_text = chat_step->mChatText; // Don't animate the nodding, as this might not blend with // other playing animations. const BOOL animate = FALSE; gChatBar->sendChatFromViewer(chat_text, CHAT_TYPE_NORMAL, animate); gesture->mCurrentStep++; break; } case STEP_WAIT: { LLGestureStepWait* wait_step = (LLGestureStepWait*)step; if (wait_step->mFlags & WAIT_FLAG_TIME) { gesture->mWaitingTimer = TRUE; gesture->mWaitTimer.reset(); } else if (wait_step->mFlags & WAIT_FLAG_ALL_ANIM) { gesture->mWaitingAnimations = TRUE; // Use the wait timer as a deadlock breaker for animation // waits. gesture->mWaitTimer.reset(); } else { gesture->mCurrentStep++; } // Don't increment instruction pointer until wait is complete. break; } default: { break; } } } // static void LLGestureManager::onLoadComplete(LLVFS *vfs, const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status) { LLLoadInfo* info = (LLLoadInfo*)user_data; LLUUID item_id = info->mItemID; BOOL inform_server = info->mInformServer; BOOL deactivate_similar = info->mDeactivateSimilar; delete info; info = NULL; gGestureManager.mLoadingCount--; if (0 == status) { LLVFile file(vfs, asset_uuid, type, LLVFile::READ); S32 size = file.getSize(); char* buffer = new char[size+1]; if (buffer == NULL) { llerrs << "Memory Allocation Failed" << llendl; return; } file.read((U8*)buffer, size); /* Flawfinder: ignore */ // ensure there's a trailing NULL so strlen will work. buffer[size] = '\0'; LLMultiGesture* gesture = new LLMultiGesture(); LLDataPackerAsciiBuffer dp(buffer, size+1); BOOL ok = gesture->deserialize(dp); if (ok) { if (deactivate_similar) { gGestureManager.deactivateSimilarGestures(gesture, item_id); // Display deactivation message if this was the last of the bunch. if (gGestureManager.mLoadingCount == 0 && gGestureManager.mDeactivateSimilarNames.length() > 0) { // we're done with this set of deactivations LLString::format_map_t args; args["[NAMES]"] = gGestureManager.mDeactivateSimilarNames; LLNotifyBox::showXml("DeactivatedGesturesTrigger", args); } } // Everything has been successful. Add to the active list. gGestureManager.mActive[item_id] = gesture; gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); if (inform_server) { // Inform the database of this change LLMessageSystem* msg = gMessageSystem; msg->newMessage("ActivateGestures"); msg->nextBlock("AgentData"); msg->addUUID("AgentID", gAgent.getID()); msg->addUUID("SessionID", gAgent.getSessionID()); msg->addU32("Flags", 0x0); msg->nextBlock("Data"); msg->addUUID("ItemID", item_id); msg->addUUID("AssetID", asset_uuid); msg->addU32("GestureFlags", 0x0); gAgent.sendReliableMessage(); } gGestureManager.notifyObservers(); } else { llwarns << "Unable to load gesture" << llendl; gGestureManager.mActive.erase(item_id); delete gesture; gesture = NULL; } delete [] buffer; buffer = NULL; } else { if( gViewerStats ) { gViewerStats->incStat( LLViewerStats::ST_DOWNLOAD_FAILED ); } if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status || LL_ERR_FILE_EMPTY == status) { LLNotifyBox::showXml("GestureMissing"); } else { LLNotifyBox::showXml("UnableToLoadGesture"); } llwarns << "Problem loading gesture: " << status << llendl; gGestureManager.mActive.erase(item_id); } } void LLGestureManager::stopGesture(LLMultiGesture* gesture) { if (!gesture) return; // Stop any animations that this gesture is currently playing std::set::const_iterator set_it; for (set_it = gesture->mRequestedAnimIDs.begin(); set_it != gesture->mRequestedAnimIDs.end(); ++set_it) { const LLUUID& anim_id = *set_it; gAgent.sendAnimationRequest(anim_id, ANIM_REQUEST_STOP); } for (set_it = gesture->mPlayingAnimIDs.begin(); set_it != gesture->mPlayingAnimIDs.end(); ++set_it) { const LLUUID& anim_id = *set_it; gAgent.sendAnimationRequest(anim_id, ANIM_REQUEST_STOP); } std::vector::iterator it; it = std::find(mPlaying.begin(), mPlaying.end(), gesture); while (it != mPlaying.end()) { mPlaying.erase(it); it = std::find(mPlaying.begin(), mPlaying.end(), gesture); } gesture->reset(); if (gesture->mDoneCallback) { gesture->mDoneCallback(gesture, gesture->mCallbackData); // callback might have deleted gesture, can't // rely on this pointer any more gesture = NULL; } notifyObservers(); } void LLGestureManager::stopGesture(const LLUUID& item_id) { item_map_t::iterator it = mActive.find(item_id); if (it == mActive.end()) return; LLMultiGesture* gesture = (*it).second; if (!gesture) return; stopGesture(gesture); } void LLGestureManager::addObserver(LLGestureManagerObserver* observer) { mObservers.push_back(observer); } void LLGestureManager::removeObserver(LLGestureManagerObserver* observer) { std::vector::iterator it; it = std::find(mObservers.begin(), mObservers.end(), observer); if (it != mObservers.end()) { mObservers.erase(it); } } // Call this method when it's time to update everyone on a new state. // Copy the list because an observer could respond by removing itself // from the list. void LLGestureManager::notifyObservers() { lldebugs << "LLGestureManager::notifyObservers" << llendl; std::vector observers = mObservers; std::vector::iterator it; for (it = observers.begin(); it != observers.end(); ++it) { LLGestureManagerObserver* observer = *it; observer->changed(); } } BOOL LLGestureManager::matchPrefix(const std::string& in_str, std::string* out_str) { S32 in_len = in_str.length(); item_map_t::iterator it; for (it = mActive.begin(); it != mActive.end(); ++it) { LLMultiGesture* gesture = (*it).second; if (gesture) { const std::string& trigger = gesture->getTrigger(); if (in_len > (S32)trigger.length()) { // too short, bail out continue; } LLString trigger_trunc = trigger; LLString::truncate(trigger_trunc, in_len); if (!LLString::compareInsensitive(in_str.c_str(), trigger_trunc.c_str())) { *out_str = trigger; return TRUE; } } } return FALSE; } void LLGestureManager::getItemIDs(std::vector* ids) { item_map_t::const_iterator it; for (it = mActive.begin(); it != mActive.end(); ++it) { ids->push_back(it->first); } }