diff --git a/indra/llcharacter/llcharacter.cpp b/indra/llcharacter/llcharacter.cpp index 25b89d2f8..65d37f8e0 100644 --- a/indra/llcharacter/llcharacter.cpp +++ b/indra/llcharacter/llcharacter.cpp @@ -194,11 +194,26 @@ void LLCharacter::updateMotions(e_update_t update_type) { if (update_type == HIDDEN_UPDATE) { + // + // Keep updating avatars that have at least one motion that is synchronized with a still running motion. + // This call tells the other controllers that we are in principle hidden. + // It returns false if we need to keep updating anyway. + if (!mMotionController.hidden(true)) + { + mMotionController.updateMotions(LLCharacter::NORMAL_UPDATE); + return; + } + // LLFastTimer t(FTM_UPDATE_HIDDEN_ANIMATION); mMotionController.updateMotionsMinimal(); } else { + // + // This call tells the other controllers that we are visible and that they need + // to keep updating if they are synchronized with us, even if they are hidden. + mMotionController.hidden(false); + // LLFastTimer t(FTM_UPDATE_ANIMATION); // unpause if the number of outstanding pause requests has dropped to the initial one if (mMotionController.isPaused() && mPauseRequest->getNumRefs() == 1) diff --git a/indra/llcharacter/llkeyframemotion.cpp b/indra/llcharacter/llkeyframemotion.cpp index aa6e05708..711ac21e5 100644 --- a/indra/llcharacter/llkeyframemotion.cpp +++ b/indra/llcharacter/llkeyframemotion.cpp @@ -818,7 +818,44 @@ void LLKeyframeMotion::onDeactivate() //----------------------------------------------------------------------------- // setStopTime() //----------------------------------------------------------------------------- -// time is in seconds since character creation +// +// Consider a looping animation of 20 frames, where the loop in point is at 3 frames +// and the loop out point at 16 frames: +// +// The first 3 frames of the animation would be the "loop in" animation. +// The last 4 frames of the animation would be the "loop out" animation. +// Frames 4 through 15 would be the looping animation frames. +// +// If the animation would not be looping, all frames would just be played once sequentially: +// +// mActivationTimestamp -. +// v +// 0 3 15 16 20 +// | | \| | +// --------------------- +// <--> <-- mLoopInPoint (relative to mActivationTimestamp) +// <--------------> <-- mLoopOutPoint (relative to mActivationTimestamp) +// <----mDuration------> +// +// When looping the animation would repeat frames 3 to 16 (loop) a few times, for example: +// +// 0 3 15 3 15 3 15 3 15 16 20 +// | | loop 1 \| loop 2 \| loop 3 \| loop 4 \| | +// ------------------------------------------------------------ +//LOOP^ ^ LOOP +// IN | <----->| OUT +// start_loop_time loop_fraction_time-' time +// +// The time at which the animation is started corresponds to frame 0 and is stored +// in mActivationTimestamp (in seconds since character creation). +// +// If setStopTime() is called with a time somewhere inside loop 4, +// then 'loop_fraction_time' is the time from the beginning of +// loop 4 till 'time'. Thus 'time - loop_fraction_time' is the first +// frame of loop 4, and '(time - loop_fraction_time) + +// (mJointMotionList->mDuration - mJointMotionList->mLoopInPoint)' +// would correspond to frame 20. +// void LLKeyframeMotion::setStopTime(F32 time) { LLMotion::setStopTime(time); @@ -836,6 +873,8 @@ void LLKeyframeMotion::setStopTime(F32 time) loop_fraction_time = fmod(time - start_loop_time, mJointMotionList->mLoopOutPoint - mJointMotionList->mLoopInPoint); } + // This sets mStopTimestamp to the time that corresponds to the end of the animation (ie, frame 20 in the above example) + // minus the ease out duration, so that the animation eases out during the loop out and finishes exactly at the end. mStopTimestamp = llmax(time, (time - loop_fraction_time) + (mJointMotionList->mDuration - mJointMotionList->mLoopInPoint) - getEaseOutDuration()); } diff --git a/indra/llcharacter/llmotion.cpp b/indra/llcharacter/llmotion.cpp index 021345fb3..94d12c7e4 100644 --- a/indra/llcharacter/llmotion.cpp +++ b/indra/llcharacter/llmotion.cpp @@ -39,6 +39,122 @@ #include "llcriticaldamp.h" #include "llmotioncontroller.h" +// +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// AISyncClientMotion class +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +AISyncKey* AISyncClientMotion::createSyncKey(AISyncKey const* from_key) const +{ + // The const cast is needed because getDuration() is non-const while it should have been. + AISyncClientMotion* self = const_cast(this); + // Only synchronize motions with the same duration and loop value. + return new AISyncKeyMotion(from_key, self->getDuration(), self->getLoop()); +} + +void AISyncClientMotion::aisync_loading(void) +{ + // Register the motion for (possible) synchronization: this marks the time at which is should have started. + unregister_client(); // In case it is already registered. Getting here means we are being (re)started now, we need to synchronize with other motions that start now. + register_client(); +} + +void AISyncClientMotion::aisync_loaded(void) +{ + AISyncServer* server = this->server(); + if (!server) + { + // Already expired without being synchronized (no other motion was started at the same time). + return; + } + AISyncKey const& key = server->key(); // The allocation of this is owned by server. + // There is no need to resync if there was not another motion started at the same time and the key already expired. + bool need_resync = !(server->never_synced() && key.expired()); + AISyncKey* new_key; + if (need_resync) + { + // Create a new key using the old start time. + new_key = createSyncKey(&key); + } + server->remove(this); // This resets mServer and might even delete server. + if (need_resync) + { + // Add the client to another server (based on the new key). This takes ownership of the key allocation. + AISyncServerMap::instance().register_client(this, new_key); + } +} + +F32 LLMotion::getRuntime(void) const +{ + llassert(mActive); + return mController->getAnimTime() - mActivationTimestamp; +} + +F32 LLMotion::getAnimTime(void) const +{ + return mController->getAnimTime(); +} + +F32 LLMotion::syncActivationTime(F32 time) +{ + AISyncServer* server = this->server(); + if (!server) + { + register_client(); + server = this->server(); + } + AISyncServer::client_list_t const& clients = server->getClients(); + if (clients.size() > 1) + { + // Look for the client with the smallest runtime. + AISyncClientMotion* motion_with_smallest_runtime = NULL; + F32 runtime = 1e10; + // Run over all motions in this to be synchronized group. + for (AISyncServer::client_list_t::const_iterator client = clients.begin(); client != clients.end(); ++client) + { + if ((client->mReadyEvents & 2)) // Is this motion active? Motions that aren't loaded yet are not active. + { + // Currently, if event 2 is set then this is an LLMotion. + llassert(dynamic_cast(client->mClientPtr)); + AISyncClientMotion* motion = static_cast(client->mClientPtr); + // Deactivated motions should have been deregistered, certainly not have event 2 set. + llassert(static_cast(motion)->isActive()); + if (motion->getRuntime() < runtime) + { + // This is a bit fuzzy since theoretically the runtime of all active motions in the list should be the same. + // Just use the smallest value to get rid of some randomness. We might even synchronizing with ourselves + // in which case 'time' would be set to a value such that mActivationTimestamp won't change. + // In practise however, this list will contain only two clients: this, being inactive, and our partner. + runtime = motion->getRuntime(); + motion_with_smallest_runtime = motion; + } + } + } + //----------------------------------------------------------------------------------------- + // Here is where the actual synchronization takes place. + // Current we only synchronize looped motions. + if (getLoop()) + { + if (motion_with_smallest_runtime) + { + // Pretend the motion was started in the past at the same time as the other motion(s). + time = getAnimTime() - runtime; + } + } + //----------------------------------------------------------------------------------------- + } + + return time; +} + +void AISyncClientMotion::deregistered(void) +{ + mReadyEvents = 0; +} +// + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // LLMotion class @@ -149,6 +265,19 @@ void LLMotion::activate(F32 time) { mActivationTimestamp = time; mStopped = FALSE; + // + if (mController && !mController->syncing_disabled()) // Avoid being registered when syncing is disabled for this motion. + { + if (mActive) + { + // If the motion is already active then we are being restarted. + // Unregister it first (if it is registered) so that the call to ready will cause it to be registered + // and be synchronized with other motions that are started at this moment. + unregister_client(); + } + ready(6, 2 | (mController->isHidden() ? 0 : 4)); // Signal that mActivationTimestamp is set/valid (2), and that this server has a visible motion (4) (or not). + } + // mActive = TRUE; onActivate(); } @@ -161,6 +290,14 @@ void LLMotion::deactivate() mActive = FALSE; mPose.setWeight(0.f); + // + if (server()) // Only when this motion is already registered. + { + ready(6, 0); // Signal that mActivationTimestamp is no longer valid. + unregister_client(); // No longer running, so no longer a part of this sync group. + } + // + if (mDeactivateCallback) { (*mDeactivateCallback)(mDeactivateCallbackUserData); diff --git a/indra/llcharacter/llmotion.h b/indra/llcharacter/llmotion.h index a67138bac..a246b08c7 100644 --- a/indra/llcharacter/llmotion.h +++ b/indra/llcharacter/llmotion.h @@ -38,6 +38,7 @@ //----------------------------------------------------------------------------- #include +#include "aisyncclient.h" #include "llerror.h" #include "llpose.h" #include "lluuid.h" @@ -45,10 +46,77 @@ class LLCharacter; class LLMotionController; +//----------------------------------------------------------------------------- +// class AISync* stuff +//----------------------------------------------------------------------------- + +class AISyncKeyMotion : public AISyncKey +{ + private: + F32 mDuration; + bool mLoop; + + public: + AISyncKeyMotion(AISyncKey const* from_key, F32 duration, bool loop) : AISyncKey(from_key), mDuration(duration), mLoop(loop) { } + + // Virtual functions of AISyncKey. + public: + /*virtual*/ synckeytype_t getkeytype(void) const + { + // Return a unique identifier for this class, where the low 8 bits represent the syncgroup. + return synckeytype_motion; + } + + /*virtual*/ bool equals(AISyncKey const& key) const + { + switch (key.getkeytype()) + { + case synckeytype_motion: + { + // The other key is of the same type. + AISyncKeyMotion const& motion_key = static_cast(key); + return mLoop == motion_key.mLoop && is_approx_equal(mDuration, motion_key.mDuration); + } + default: + // The keys must be in the same syncgroup. + break; + } + return false; + } +}; + +class AISyncClientMotion : public AISyncClient +{ + protected: + // Make sure that clients that are destroyed are first unregistered. + // This is needed, for example, when loading fails or when excess motions are being purged. + /*virtual*/ ~AISyncClientMotion() { unregister_client(); } + + // AISyncClient events. + /*virtual*/ AISyncKey* createSyncKey(AISyncKey const* from_key = NULL) const; + /*virtual*/ void event1_ready(void) { } + /*virtual*/ void event1_not_ready(void) { } + /*virtual*/ void deregistered(void); + + protected: + // This is called when the server sent us a message that it wants us to play this animation, but we can't because it isn't fully downloaded yet. + void aisync_loading(void); + // This is called when that motion is successfully loaded and it has to be re-registered because now the duration etc is known. + void aisync_loaded(void); + + public: + // Virtual functions of AISyncClientMotion. + // These are defined by classes derived from LLMotion (which is derived from this class). + virtual BOOL getLoop() = 0; + virtual F32 getDuration() = 0; + virtual F32 getAnimTime(void) const = 0; + virtual F32 getRuntime(void) const = 0; +}; + //----------------------------------------------------------------------------- // class LLMotion //----------------------------------------------------------------------------- -class LLMotion +class LLMotion : public AISyncClientMotion { friend class LLMotionController; @@ -115,7 +183,22 @@ protected: BOOL isActive() { return mActive; } public: void activate(F32 time); - + + // + // Returns the time that this motion has been running. + virtual F32 getRuntime(void) const; + + // Return the current time (in seconds since creation of the controller). + virtual F32 getAnimTime(void) const; + + // This is called when a motion is to be activated, but might need synchronization. + // It adjusts the start time to match that of other motions in the same synchronization group that were already started. + F32 syncActivationTime(F32 time); + + // Accessor. + LLMotionController* getController(void) const { return mController; } + // + public: //------------------------------------------------------------------------- // animation callbacks to be implemented by subclasses diff --git a/indra/llcharacter/llmotioncontroller.cpp b/indra/llcharacter/llmotioncontroller.cpp index 099805676..8f8475576 100644 --- a/indra/llcharacter/llmotioncontroller.cpp +++ b/indra/llcharacter/llmotioncontroller.cpp @@ -130,6 +130,9 @@ LLMotionController::LLMotionController() mTimeFactor(sCurrentTimeFactor), mCharacter(NULL), mActiveMask(0), + mDisableSyncing(0), + mHidden(false), + mHaveVisibleSyncedMotions(false), mPrevTimerElapsed(0.f), mAnimTime(0.f), mLastTime(0.0f), @@ -173,6 +176,10 @@ void LLMotionController::deleteAllMotions() mActiveMask = 0; for_each(mDeprecatedMotions.begin(), mDeprecatedMotions.end(), DeletePointer()); mDeprecatedMotions.clear(); + for (motion_map_t::iterator iter = mAllMotions.begin(); iter != mAllMotions.end(); ++iter) + { + iter->second->unregister_client(); + } // for_each(mAllMotions.begin(), mAllMotions.end(), DeletePairedPointer()); mAllMotions.clear(); @@ -437,7 +444,19 @@ BOOL LLMotionController::startMotion(const LLUUID &id, F32 start_offset) } // llinfos << "Starting motion " << name << llendl; - return activateMotionInstance(motion, mAnimTime - start_offset); + // + F32 start_time = mAnimTime - start_offset; + if (!mDisableSyncing) + { + start_time = motion->syncActivationTime(start_time); + } + ++mDisableSyncing; + // + BOOL res = activateMotionInstance(motion, start_time); + // + --mDisableSyncing; + // + return res; } @@ -793,7 +812,19 @@ void LLMotionController::updateLoadingMotions() // this motion should be playing if (!motionp->isStopped()) { - activateMotionInstance(motionp, mAnimTime); + // + F32 start_time = mAnimTime; + if (!mDisableSyncing) + { + motionp->aisync_loaded(); + start_time = motionp->syncActivationTime(start_time); + } + ++mDisableSyncing; + // + activateMotionInstance(motionp, start_time); + // + --mDisableSyncing; + // } } else if (status == LLMotion::STATUS_FAILURE) @@ -806,6 +837,10 @@ void LLMotionController::updateLoadingMotions() // check for it's existence there. llassert(mDeprecatedMotions.find(motionp) == mDeprecatedMotions.end()); mAllMotions.erase(motionp->getID()); + // + // Make sure we're not registered anymore. + motionp->unregister_client(); + // delete motionp; } } @@ -933,6 +968,12 @@ BOOL LLMotionController::activateMotionInstance(LLMotion *motion, F32 time) if (mLoadingMotions.find(motion) != mLoadingMotions.end()) { + // + if (!syncing_disabled()) + { + motion->aisync_loading(); + } + // // we want to start this motion, but we can't yet, so flag it as started motion->setStopped(FALSE); // report pending animations as activated @@ -1094,9 +1135,9 @@ void LLMotionController::deactivateAllMotions() { // Singu note: this must run over mActiveMotions: other motions are not active, // and running over mAllMotions will miss the ones in mDeprecatedMotions. - for (motion_list_t::iterator iter = mActiveMotions.begin(); iter != mActiveMotions.end(); ++iter) + for (motion_list_t::iterator iter = mActiveMotions.begin(); iter != mActiveMotions.end();) { - deactivateMotionInstance(*iter); + deactivateMotionInstance(*iter++); // This might invalidate iter by erasing it from mActiveMotions. } } @@ -1126,13 +1167,98 @@ void LLMotionController::flushAllMotions() mCharacter->removeAnimationData("Hand Pose"); // restart motions + // + // Because we called motionp->deactivate() above, instead of deactivateMotionInstance(), + // prevent calling AISyncClientMotion::activateInstance in startMotion below. + disable_syncing(); + // for (std::vector >::iterator iter = active_motions.begin(); iter != active_motions.end(); ++iter) { startMotion(iter->first, iter->second); } + // + enable_syncing(); + // } +// +//----------------------------------------------------------------------------- +// toggle_hidden() +//----------------------------------------------------------------------------- +void LLMotionController::toggle_hidden(void) +{ + mHaveVisibleSyncedMotions = mHidden; // Default is false if we just became invisible (otherwise this value isn't used). + mHidden = !mHidden; + synceventset_t const visible = mHidden ? 0 : 4; + + // Run over all motions. + for (motion_list_t::iterator iter = mActiveMotions.begin(); iter != mActiveMotions.end(); ++iter) + { + LLMotion* motionp = *iter; + AISyncServer* server = motionp->server(); + if (server && !server->never_synced() && (motionp->mReadyEvents & 2)) // Skip motions that aren't synchronized at all or that are not active. + { + bool visible_before = server->events_with_at_least_one_client_ready() & 4; + server->ready(4, visible, motionp); // Mark that now we are visible or no longer visible. + bool visible_after = server->events_with_at_least_one_client_ready() & 4; + if (visible_after) // Are there any synchronized motions (left) that ARE visible? + { + mHaveVisibleSyncedMotions = true; + } + if (visible_before != visible_after) + { + // The group as a whole now might need to change whether or not it is animated. + AISyncServer::client_list_t const& clients = server->getClients(); + for (AISyncServer::client_list_t::const_iterator client = clients.begin(); client != clients.end(); ++client) + { + LLMotion* motion = dynamic_cast(client->mClientPtr); + if (!motion) + { + continue; + } + LLMotionController* controller = motion->getController(); + if (controller == this) + { + continue; + } + if (visible_after) + { + // Us becoming visible means that all synchronized avatars need to be animated again too. + controller->setHaveVisibleSyncedMotions(); + } + else + { + // Us becoming hidden means that all synchronized avatars might stop animating. + controller->refresh_hidden(); // It is extremely unlikely, but harmless, to call this twice on the same controller. + } + } + } + } + } +} + +void LLMotionController::refresh_hidden(void) +{ + mHaveVisibleSyncedMotions = !mHidden; + + // Run over all motions. + for (motion_list_t::iterator iter = mActiveMotions.begin(); iter != mActiveMotions.end(); ++iter) + { + LLMotion* motionp = *iter; + AISyncServer* server = motionp->server(); + if (server && !server->never_synced() && (motionp->mReadyEvents & 2)) // Skip motions that aren't synchronized at all or that are not active. + { + bool visible_after = server->events_with_at_least_one_client_ready() & 4; + if (visible_after) // Are there any synchronized motions (left) that ARE visible? + { + mHaveVisibleSyncedMotions = true; + } + } + } +} +// + //----------------------------------------------------------------------------- // pause() //----------------------------------------------------------------------------- diff --git a/indra/llcharacter/llmotioncontroller.h b/indra/llcharacter/llmotioncontroller.h index 04b15ff89..ac3d6d1f2 100644 --- a/indra/llcharacter/llmotioncontroller.h +++ b/indra/llcharacter/llmotioncontroller.h @@ -150,11 +150,11 @@ public: //Flush is a liar. void deactivateAllMotions(); - // + // void activated(U32 bit) { mActiveMask |= bit; } void deactivated(U32 bit) { mActiveMask &= ~bit; } bool isactive(U32 bit) const { return (mActiveMask & bit) != 0; } - // + // // pause and continue all motions void pauseAllMotions(); @@ -186,7 +186,10 @@ protected: // internal operations act on motion instances directly // as there can be duplicate motions per id during blending overlap void deleteAllMotions(); + // singu: LLMotion needs access to activateMotionInstance. +public: BOOL activateMotionInstance(LLMotion *motion, F32 time); +protected: BOOL deactivateMotionInstance(LLMotion *motion); void deprecateMotionInstance(LLMotion* motion); BOOL stopMotionInstance(LLMotion *motion, BOOL stop_imemdiate); @@ -227,9 +230,12 @@ protected: motion_list_t mActiveMotions; motion_set_t mDeprecatedMotions; - // + // U32 mActiveMask; - // + int mDisableSyncing; // Set while LLMotion::onActivate (and onDeactivate) are called for this controller. + bool mHidden; // The value of the last call to hidden(). + bool mHaveVisibleSyncedMotions; // Set when we are synchronized with one or more motions of a controller that is not hidden. + // LLFrameTimer mTimer; F32 mPrevTimerElapsed; F32 mAnimTime; @@ -242,6 +248,26 @@ protected: F32 mLastInterp; U8 mJointSignature[2][LL_CHARACTER_MAX_JOINTS]; + + // +public: + // Internal administration for AISync. + void disable_syncing(void) { mDisableSyncing += 100; } + void enable_syncing(void) { mDisableSyncing -= 100; } + bool syncing_disabled(void) const { return mDisableSyncing >= 100; } + + // Accessors needed for synchronization. + F32 getAnimTime(void) const { return mAnimTime; } + bool isHidden(void) const { return mHidden; } + + // Called often. Should return false if we still need to keep updating our motions even if we're not visible. + bool hidden(bool not_visible) { if (mHidden != not_visible) toggle_hidden(); return !mHaveVisibleSyncedMotions; } + +private: + void toggle_hidden(void); + void refresh_hidden(void); + void setHaveVisibleSyncedMotions(void) { mHaveVisibleSyncedMotions = true; } + // }; //----------------------------------------------------------------------------- diff --git a/indra/llcommon/aisyncclient.cpp b/indra/llcommon/aisyncclient.cpp index 9cadb3cb1..e1865fec8 100644 --- a/indra/llcommon/aisyncclient.cpp +++ b/indra/llcommon/aisyncclient.cpp @@ -126,20 +126,13 @@ void print_clients(AISyncServer const* server, AISyncServer::client_list_t const } #endif -void AISyncServerMap::register_client(AISyncClient* client) +void AISyncServerMap::register_client(AISyncClient* client, AISyncKey* new_key) { -#ifdef DEBUG_SYNCOUTPUT - DoutEntering(dc::notice, "AISyncServerMap::register_client(" << client << ")"); -#endif - // client must always be a new client that has to be stored somewhere. llassert(client->server() == NULL); // Obviously the client can't be ready for anything when it isn't registered yet. llassert(!client->mReadyEvents); - // First we need its sync key. - AISyncKey* new_key = client->createSyncKey(); - // Find if a server with this key already exists. AISyncServer* server = NULL; for (server_list_t::iterator iter = mServers.begin(); iter != mServers.end();) @@ -183,7 +176,6 @@ void AISyncServerMap::register_client(AISyncClient* client) server = new AISyncServer(new_key); // Add it to mServers, before the last server that is younger then the new key. server_list_t::iterator where = mServers.end(); // Insert the new server before 'where', -#if 0 // This is probably not necessary. server_list_t::iterator new_where = where; while (where != mServers.begin()) // unless there exists a server before that { @@ -194,10 +186,6 @@ void AISyncServerMap::register_client(AISyncClient* client) } where = new_where; // then insert it before that element (etc). } -#elif defined(SHOW_ASSERT) - server_list_t::iterator new_where = where; - llassert(where == mServers.begin() || new_key->getCreationTime() >= (*--new_where)->key().getCreationTime()); -#endif // This method causes a single call to intrusive_ptr_add_ref and none to intrusive_ptr_release. server_ptr_t server_ptr = server; mServers.insert(where, server_ptr_t())->swap(server_ptr); @@ -231,10 +219,6 @@ void AISyncServer::sanity_check(void) const void AISyncServer::add(AISyncClient* client) { -#ifdef DEBUG_SYNCOUTPUT - DoutEntering(dc::notice, "AISyncServer::add(" << client << "), with this = " << this); - print_clients(this, getClients()); -#endif #ifdef SYNC_TESTSUITE sanity_check(); #endif @@ -263,16 +247,10 @@ void AISyncServer::add(AISyncClient* client) void AISyncServer::remove(AISyncClient* client) { -#ifdef DEBUG_SYNCOUTPUT - DoutEntering(dc::notice, "AISyncServer::remove(" << client << "), with this = " << this); - print_clients(this, getClients()); -#endif #ifdef SYNC_TESTSUITE sanity_check(); #endif - // A client may only be unregistered after it was marked not-ready for all events. - llassert(!client->mReadyEvents); client_list_t::iterator client_iter = mClients.begin(); synceventset_t remaining_ready_events = (synceventset_t)-1; // All clients are ready. synceventset_t remaining_pending_events = 0; // At least one client is ready (waiting for the other clients thus). @@ -292,15 +270,14 @@ void AISyncServer::remove(AISyncClient* client) } llassert(found_client != mClients.end()); // This must be the same as client->mReadyEvents. - llassert(!found_client->mReadyEvents); + llassert(found_client->mReadyEvents == client->mReadyEvents); mClients.erase(found_client); - client->mServer.reset(); synceventset_t old_ready_events = mReadyEvents; mReadyEvents = remaining_ready_events; - // Since client->mReadyEvents is zero, this should be the same. - llassert(mPendingEvents == remaining_pending_events); mPendingEvents = remaining_pending_events; trigger(old_ready_events); + client->mServer.reset(); + client->deregistered(); #ifdef SYNC_TESTSUITE sanity_check(); @@ -309,10 +286,6 @@ void AISyncServer::remove(AISyncClient* client) void AISyncServer::unregister_last_client(void) { -#ifdef DEBUG_SYNCOUTPUT - DoutEntering(dc::notice, "AISyncServer::unregister_last_client()"); - print_clients(this, getClients()); -#endif #ifdef SYNC_TESTSUITE sanity_check(); #endif @@ -333,22 +306,6 @@ void AISyncServer::unregister_last_client(void) #endif } -synceventset_t AISyncServer::events_with_all_clients_ready(void) const -{ -#ifdef SYNC_TESTSUITE - sanity_check(); -#endif - return mReadyEvents; -} - -synceventset_t AISyncServer::events_with_at_least_one_client_ready(void) const -{ -#ifdef SYNC_TESTSUITE - sanity_check(); -#endif - return mPendingEvents; -} - void AISyncServer::trigger(synceventset_t old_ready_events) { // If event 1 changed, informat all clients about it. @@ -368,22 +325,14 @@ void AISyncServer::trigger(synceventset_t old_ready_events) } } -bool AISyncServer::ready(synceventset_t events, synceventset_t yesno, AISyncClient* client) +void AISyncServer::ready(synceventset_t events, synceventset_t yesno, AISyncClient* client) { -#ifdef DEBUG_SYNCOUTPUT - DoutEntering(dc::notice, "AISyncServer::ready(" << SyncEventSet(events) << ", " << SyncEventSet(yesno) << ", " << client << ")"); - print_clients(this, getClients()); -#endif #ifdef SYNC_TESTSUITE sanity_check(); #endif synceventset_t added_events = events & yesno; synceventset_t removed_events = events & ~yesno; - // May not add events that are already ready. - llassert(!(client->mReadyEvents & added_events)); - // Cannot remove events that weren't ready. - llassert((client->mReadyEvents & removed_events) == removed_events); // Run over all clients to find the client and calculate the current state. synceventset_t remaining_ready_events = (synceventset_t)-1; // All clients are ready. diff --git a/indra/llcommon/aisyncclient.h b/indra/llcommon/aisyncclient.h index 34a3644a3..b810f143a 100644 --- a/indra/llcommon/aisyncclient.h +++ b/indra/llcommon/aisyncclient.h @@ -117,9 +117,16 @@ class LL_COMMON_API AISyncKey public: // Constructor. - AISyncKey(void) : mStartFrameCount(LLFrameTimer::getFrameCount()) + AISyncKey(AISyncKey const* from_key) : mStartFrameCount(from_key ? from_key->mStartFrameCount : LLFrameTimer::getFrameCount()) { - mFrameTimer.reset(sExpirationTime); + if (from_key) + { + mFrameTimer.copy(from_key->mFrameTimer); + } + else + { + mFrameTimer.reset(sExpirationTime); + } } // Destructor. @@ -186,7 +193,7 @@ class LL_COMMON_API AISyncServer // Add a new client to this server. void add(AISyncClient* client); - // Add a new client to this server. + // Remove a client from this server. void remove(AISyncClient* client); // Return the key associated to this server (which is the key produced by the first client of the largest synckeytype_t that was added). @@ -198,20 +205,19 @@ class LL_COMMON_API AISyncServer bool never_synced(void) const { return !mSynchronized; } // Set readiness of all events at once. - bool ready(synceventset_t events, synceventset_t yesno, AISyncClient* client); + void ready(synceventset_t events, synceventset_t yesno, AISyncClient* client); // Unregister the (only) client because it's own its own and will never need synchronization. void unregister_last_client(void); // Return the events that all clients for. - synceventset_t events_with_all_clients_ready(void) const; + synceventset_t events_with_all_clients_ready(void) const { return mReadyEvents; } // Return events that at least one client is ready for. - synceventset_t events_with_at_least_one_client_ready(void) const; + synceventset_t events_with_at_least_one_client_ready(void) const { return mPendingEvents; } -#ifdef SHOW_ASSERT + // Return a list of all registered clients. client_list_t const& getClients(void) const { return mClients; } -#endif private: // Call event1_ready() or event1_not_ready() on all clients if the least significant bit of mReadyEvents changed. @@ -238,7 +244,7 @@ class LL_COMMON_API AISyncServerMap : public LLSingleton public: // Find or create a server object that the client belongs to and store the client in it. // If a new server is created, it is stored in mServers. - void register_client(AISyncClient* client); + void register_client(AISyncClient* client, AISyncKey* new_key); private: friend void intrusive_ptr_release(AISyncServer* server); @@ -258,8 +264,8 @@ class LL_COMMON_API AISyncClient synceventset_t mReadyEvents; AISyncClient(void) : mReadyEvents(0) { } #endif - virtual ~AISyncClient() { } - virtual AISyncKey* createSyncKey(void) const = 0; + virtual ~AISyncClient() { llassert(!mServer); /* If this fails then you need to add unregister_client() to the top of the destructor of the derived class that implements deregistered(). */ } + virtual AISyncKey* createSyncKey(AISyncKey const* from_key = NULL) const = 0; virtual void event1_ready(void) = 0; virtual void event1_not_ready(void) = 0; @@ -275,21 +281,22 @@ class LL_COMMON_API AISyncClient AISyncServer* server(void) const { return mServer.get(); } // Add this client to a server with matching sync key. Optionally the server is first created. - void register_client(void) { AISyncServerMap::instance().register_client(this); } + void register_client(void) { AISyncServerMap::instance().register_client(this, createSyncKey()); } - // Remove this client from its server. - void unregister_client(void) { mServer->remove(this); } + // Remove this client from its server, if any. + void unregister_client(void) { if (mServer) mServer->remove(this); } // Call 'ready' when you are ready (or not) to get a call to start(). // Returns true if that call was made (immediately), otherwise it may happen later. - bool ready(synceventset_t events, synceventset_t yesno) // Set readiness of all events at once. + // Set readiness of all events at once. + void ready(synceventset_t events, synceventset_t yesno) { if (!mServer) { register_client(); } - return mServer->ready(events, yesno, this); + mServer->ready(events, yesno, this); } }; diff --git a/indra/llcommon/llframetimer.h b/indra/llcommon/llframetimer.h index 2813150c5..30bc58dd9 100644 --- a/indra/llcommon/llframetimer.h +++ b/indra/llcommon/llframetimer.h @@ -47,6 +47,10 @@ public: // Create an LLFrameTimer and start it. After creation it is running and in the state expired (hasExpired will return true). LLFrameTimer(void) : mExpiry(0), mRunning(true), mPaused(false) { if (!sGlobalMutex) global_initialization(); setAge(0.0); } + // + void copy(LLFrameTimer const& timer) { mStartTime = timer.mStartTime; mExpiry = timer.mExpiry; mRunning = timer.mRunning; mPaused = timer.mPaused; } + // + // Atomic reads of static variables. // Return the number of seconds since the start of the application. @@ -142,6 +146,9 @@ public: bool hasExpired() const { return getElapsedSeconds() >= mExpiry; } F32 getElapsedTimeF32() const { llassert(mRunning); return mPaused ? (F32)mStartTime : (F32)(getElapsedSeconds() - mStartTime); } bool getStarted() const { return mRunning; } + // + F64 getStartTime() const { llassert(!mPaused); return mStartTime; } + // // return the seconds since epoch when this timer will expire. F64 expiresAt() const; diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index a2adc49a9..ceea596b0 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -5772,7 +5772,7 @@ void process_avatar_animation(LLMessageSystem *mesgsys, void **user_data) } } - if (num_blocks) + //if (num_blocks) Singu note: commented out; having blocks or not is totally irrelevant! { avatarp->processAnimationStateChanges(); } diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index b34291a5f..8562edb7a 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -231,7 +231,9 @@ void LLVOAvatar::startMotion(U32 bit, F32 time_offset) { if (!isMotionActive(bit)) { + mMotionController.disable_syncing(); // Don't attempt to synchronize AIMaskedMotion. startMotion(mask2ID(bit), time_offset); + mMotionController.enable_syncing(); } } @@ -3810,6 +3812,7 @@ BOOL LLVOAvatar::updateCharacter(LLAgent &agent) if (LLVOAvatar::sShowAnimationDebug) { + addDebugText(llformat("at=%.1f", mMotionController.getAnimTime())); for (LLMotionController::motion_list_t::iterator iter = mMotionController.getActiveMotions().begin(); iter != mMotionController.getActiveMotions().end(); ++iter) { @@ -3829,6 +3832,10 @@ BOOL LLVOAvatar::updateCharacter(LLAgent &agent) motionp->getName().c_str(), (U32)motionp->getPriority()); } + if (motionp->server()) + { + output += llformat(" rt=%.1f r=%d s=0x%xl", motionp->getRuntime(), motionp->mReadyEvents, motionp->server()); + } addDebugText(output); } } @@ -5499,6 +5506,7 @@ void LLVOAvatar::processAnimationStateChanges() } // clear all current animations + BOOL const AOEnabled = gSavedSettings.getBOOL("AOEnabled"); // Singu note: put this outside the loop. AnimIterator anim_it; for (anim_it = mPlayingAnimations.begin(); anim_it != mPlayingAnimations.end();) { @@ -5508,9 +5516,9 @@ void LLVOAvatar::processAnimationStateChanges() if (found_anim == mSignaledAnimations.end()) { - if (isSelf()) + if (AOEnabled && isSelf()) { - if ((gSavedSettings.getBOOL("AOEnabled")) && LLFloaterAO::stopMotion(anim_it->first, FALSE)) // if the AO replaced this anim serverside then stop it serverside + if (LLFloaterAO::stopMotion(anim_it->first, FALSE)) // if the AO replaced this anim serverside then stop it serverside { // return TRUE; //no local stop needed } @@ -5540,7 +5548,7 @@ void LLVOAvatar::processAnimationStateChanges() // if (processSingleAnimationStateChange(anim_it->first, TRUE)) { - if (isSelf() && gSavedSettings.getBOOL("AOEnabled")) // AO is only for ME + if (AOEnabled && isSelf()) // AO is only for ME { LLFloaterAO::startMotion(anim_it->first, 0,FALSE); // AO overrides the anim if needed }