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
}