diff --git a/indra/cwdebug/debug.h b/indra/cwdebug/debug.h index 3c59db690..58e0c6ac9 100644 --- a/indra/cwdebug/debug.h +++ b/indra/cwdebug/debug.h @@ -392,6 +392,40 @@ void InstanceTracker::dump(void) } // namespace debug +template +class AIDebugInstanceCounter +{ + public: + static int sInstanceCount; + + protected: + static void print_count(char const* name, int count, bool destruction); + + AIDebugInstanceCounter() + { + print_count(typeid(T).name(), ++sInstanceCount, false); + } + AIDebugInstanceCounter(AIDebugInstanceCounter const&) + { + print_count(typeid(T).name(), ++sInstanceCount, false); + } + ~AIDebugInstanceCounter() + { + print_count(typeid(T).name(), --sInstanceCount, true); + } +}; + +//static +template +int AIDebugInstanceCounter::sInstanceCount; + +//static +template +void AIDebugInstanceCounter::print_count(char const* name, int count, bool destruction) +{ + Dout(dc::notice, (destruction ? "Destructed " : "Constructing ") << name << ", now " << count << " instance" << ((count == 1) ? "." : "s.")); +} + //! Debugging macro. // // Print "Entering " << \a data to channel \a cntrl and increment diff --git a/indra/llcharacter/CMakeLists.txt b/indra/llcharacter/CMakeLists.txt index 6652333ef..8dda476d9 100644 --- a/indra/llcharacter/CMakeLists.txt +++ b/indra/llcharacter/CMakeLists.txt @@ -29,7 +29,6 @@ set(llcharacter_SOURCE_FILES lljointsolverrp3.cpp llkeyframefallmotion.cpp llkeyframemotion.cpp - llkeyframemotionparam.cpp llkeyframestandmotion.cpp llkeyframewalkmotion.cpp llmotion.cpp @@ -57,7 +56,6 @@ set(llcharacter_HEADER_FILES lljointstate.h llkeyframefallmotion.h llkeyframemotion.h - llkeyframemotionparam.h llkeyframestandmotion.h llkeyframewalkmotion.h llmotion.h diff --git a/indra/llcharacter/llcharacter.cpp b/indra/llcharacter/llcharacter.cpp index 888c20cd4..12960ac01 100644 --- a/indra/llcharacter/llcharacter.cpp +++ b/indra/llcharacter/llcharacter.cpp @@ -136,7 +136,6 @@ LLMotion* LLCharacter::findMotion( const LLUUID &id ) //----------------------------------------------------------------------------- // createMotion() -// NOTE: Always assign the result to a LLPointer! //----------------------------------------------------------------------------- LLMotion* LLCharacter::createMotion( const LLUUID &id ) { @@ -195,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) @@ -529,3 +543,17 @@ LLAnimPauseRequest LLCharacter::requestPause() return mPauseRequest; } +void LLCharacter::requestPause(std::vector& avatar_pause_handles) +{ + mMotionController.pauseAllMotions(); + avatar_pause_handles.push_back(mPauseRequest); +} + +void LLCharacter::pauseAllSyncedCharacters(std::vector& avatar_pause_handles) +{ + // Pause this avatar. + requestPause(avatar_pause_handles); + // Also pause all avatars with synchronized motions. + mMotionController.pauseAllSyncedCharacters(avatar_pause_handles); +} + diff --git a/indra/llcharacter/llcharacter.h b/indra/llcharacter/llcharacter.h index 2b872effa..f618b446f 100644 --- a/indra/llcharacter/llcharacter.h +++ b/indra/llcharacter/llcharacter.h @@ -146,6 +146,7 @@ public: // is this motion active? BOOL isMotionActive( const LLUUID& id ); + bool isMotionActive(U32 bit) const { return mMotionController.isactive(bit); } // Event handler for motion deactivation. // Called when a motion has completely stopped and has been deactivated. @@ -158,6 +159,8 @@ public: void updateMotions(e_update_t update_type); LLAnimPauseRequest requestPause(); + void requestPause(std::vector& avatar_pause_handles); + void pauseAllSyncedCharacters(std::vector& avatar_pause_handles); BOOL areAnimationsPaused() const { return mMotionController.isPaused(); } void setAnimTimeFactor(F32 factor) { mMotionController.setTimeFactor(factor); } void setTimeStep(F32 time_step) { mMotionController.setTimeStep(time_step); } diff --git a/indra/llcharacter/lleditingmotion.cpp b/indra/llcharacter/lleditingmotion.cpp index c2a87d80a..d07f37779 100644 --- a/indra/llcharacter/lleditingmotion.cpp +++ b/indra/llcharacter/lleditingmotion.cpp @@ -49,7 +49,7 @@ S32 LLEditingMotion::sHandPosePriority = 3; // LLEditingMotion() // Class Constructor //----------------------------------------------------------------------------- -LLEditingMotion::LLEditingMotion( const LLUUID &id) : LLMotion(id) +LLEditingMotion::LLEditingMotion(LLUUID const& id, LLMotionController* controller) : AIMaskedMotion(id, controller, ANIM_AGENT_EDITING) { mCharacter = NULL; @@ -155,7 +155,7 @@ BOOL LLEditingMotion::onActivate() mShoulderJoint.setRotation( mShoulderState->getJoint()->getRotation() ); mElbowJoint.setRotation( mElbowState->getJoint()->getRotation() ); - return TRUE; + return AIMaskedMotion::onActivate(); } //----------------------------------------------------------------------------- @@ -256,12 +256,4 @@ BOOL LLEditingMotion::onUpdate(F32 time, U8* joint_mask) return result; } -//----------------------------------------------------------------------------- -// LLEditingMotion::onDeactivate() -//----------------------------------------------------------------------------- -void LLEditingMotion::onDeactivate() -{ -} - - // End diff --git a/indra/llcharacter/lleditingmotion.h b/indra/llcharacter/lleditingmotion.h index 4a83d4b9f..bc95ff682 100644 --- a/indra/llcharacter/lleditingmotion.h +++ b/indra/llcharacter/lleditingmotion.h @@ -49,11 +49,11 @@ // class LLEditingMotion //----------------------------------------------------------------------------- class LLEditingMotion : - public LLMotion + public AIMaskedMotion { public: // Constructor - LLEditingMotion(const LLUUID &id); + LLEditingMotion(LLUUID const& id, LLMotionController* controller); // Destructor virtual ~LLEditingMotion(); @@ -65,7 +65,7 @@ public: // static constructor // all subclasses must implement such a function and register it - static LLMotion *create(const LLUUID &id) { return new LLEditingMotion(id); } + static LLMotion* create(LLUUID const& id, LLMotionController* controller) { return new LLEditingMotion(id, controller); } public: //------------------------------------------------------------------------- @@ -107,9 +107,6 @@ public: // must return FALSE when the motion is completed. virtual BOOL onUpdate(F32 time, U8* joint_mask); - // called when a motion is deactivated - virtual void onDeactivate(); - public: //------------------------------------------------------------------------- // joint states to be animated diff --git a/indra/llcharacter/llhandmotion.cpp b/indra/llcharacter/llhandmotion.cpp index 696dba0d9..be6dbb050 100644 --- a/indra/llcharacter/llhandmotion.cpp +++ b/indra/llcharacter/llhandmotion.cpp @@ -61,7 +61,7 @@ const F32 HAND_MORPH_BLEND_TIME = 0.2f; // LLHandMotion() // Class Constructor //----------------------------------------------------------------------------- -LLHandMotion::LLHandMotion(const LLUUID &id) : LLMotion(id) +LLHandMotion::LLHandMotion(LLUUID const& id, LLMotionController* controller) : AIMaskedMotion(id, controller, ANIM_AGENT_HAND_MOTION) { mCharacter = NULL; mLastTime = 0.f; @@ -112,7 +112,7 @@ BOOL LLHandMotion::onActivate() mCharacter->setVisualParamWeight(gHandPoseNames[mCurrentPose], 1.f); mCharacter->updateVisualParams(); } - return TRUE; + return AIMaskedMotion::onActivate(); } @@ -235,14 +235,6 @@ BOOL LLHandMotion::onUpdate(F32 time, U8* joint_mask) return TRUE; } - -//----------------------------------------------------------------------------- -// LLHandMotion::onDeactivate() -//----------------------------------------------------------------------------- -void LLHandMotion::onDeactivate() -{ -} - //----------------------------------------------------------------------------- // LLHandMotion::getHandPoseName() //----------------------------------------------------------------------------- diff --git a/indra/llcharacter/llhandmotion.h b/indra/llcharacter/llhandmotion.h index dcf169662..012c3c6be 100644 --- a/indra/llcharacter/llhandmotion.h +++ b/indra/llcharacter/llhandmotion.h @@ -45,7 +45,7 @@ // class LLHandMotion //----------------------------------------------------------------------------- class LLHandMotion : - public LLMotion + public AIMaskedMotion { public: typedef enum e_hand_pose @@ -68,7 +68,7 @@ public: } eHandPose; // Constructor - LLHandMotion(const LLUUID &id); + LLHandMotion(LLUUID const& id, LLMotionController* controller); // Destructor virtual ~LLHandMotion(); @@ -80,7 +80,7 @@ public: // static constructor // all subclasses must implement such a function and register it - static LLMotion *create(const LLUUID &id) { return new LLHandMotion(id); } + static LLMotion* create(LLUUID const& id, LLMotionController* controller) { return new LLHandMotion(id, controller); } public: //------------------------------------------------------------------------- @@ -122,9 +122,6 @@ public: // must return FALSE when the motion is completed. virtual BOOL onUpdate(F32 time, U8* joint_mask); - // called when a motion is deactivated - virtual void onDeactivate(); - virtual BOOL canDeprecate() { return FALSE; } static std::string getHandPoseName(eHandPose pose); diff --git a/indra/llcharacter/llheadrotmotion.cpp b/indra/llcharacter/llheadrotmotion.cpp index 0ee378f3b..79912f9d5 100644 --- a/indra/llcharacter/llheadrotmotion.cpp +++ b/indra/llcharacter/llheadrotmotion.cpp @@ -76,8 +76,8 @@ const F32 EYE_BLINK_TIME_DELTA = 0.005f; // time between one eye starting a blin // LLHeadRotMotion() // Class Constructor //----------------------------------------------------------------------------- -LLHeadRotMotion::LLHeadRotMotion(const LLUUID &id) : - LLMotion(id), +LLHeadRotMotion::LLHeadRotMotion(LLUUID const& id, LLMotionController* controller) : + AIMaskedMotion(id, controller, ANIM_AGENT_HEAD_ROT), mCharacter(NULL), mTorsoJoint(NULL), mHeadJoint(NULL) @@ -104,7 +104,10 @@ LLHeadRotMotion::~LLHeadRotMotion() LLMotion::LLMotionInitStatus LLHeadRotMotion::onInitialize(LLCharacter *character) { if (!character) + { + llwarns << "character is NULL." << llendl; return STATUS_FAILURE; + } mCharacter = character; mPelvisJoint = character->getJoint("mPelvis"); @@ -169,16 +172,6 @@ LLMotion::LLMotionInitStatus LLHeadRotMotion::onInitialize(LLCharacter *characte return STATUS_SUCCESS; } - -//----------------------------------------------------------------------------- -// LLHeadRotMotion::onActivate() -//----------------------------------------------------------------------------- -BOOL LLHeadRotMotion::onActivate() -{ - return TRUE; -} - - //----------------------------------------------------------------------------- // LLHeadRotMotion::onUpdate() //----------------------------------------------------------------------------- @@ -263,19 +256,11 @@ BOOL LLHeadRotMotion::onUpdate(F32 time, U8* joint_mask) } -//----------------------------------------------------------------------------- -// LLHeadRotMotion::onDeactivate() -//----------------------------------------------------------------------------- -void LLHeadRotMotion::onDeactivate() -{ -} - - //----------------------------------------------------------------------------- // LLEyeMotion() // Class Constructor //----------------------------------------------------------------------------- -LLEyeMotion::LLEyeMotion(const LLUUID &id) : LLMotion(id) +LLEyeMotion::LLEyeMotion(LLUUID const& id, LLMotionController* controller) : AIMaskedMotion(id, controller, ANIM_AGENT_EYE) { mCharacter = NULL; mEyeJitterTime = 0.f; @@ -343,16 +328,6 @@ LLMotion::LLMotionInitStatus LLEyeMotion::onInitialize(LLCharacter *character) return STATUS_SUCCESS; } - -//----------------------------------------------------------------------------- -// LLEyeMotion::onActivate() -//----------------------------------------------------------------------------- -BOOL LLEyeMotion::onActivate() -{ - return TRUE; -} - - //----------------------------------------------------------------------------- // LLEyeMotion::onUpdate() //----------------------------------------------------------------------------- @@ -533,6 +508,8 @@ void LLEyeMotion::onDeactivate() { joint->setRotation(LLQuaternion::DEFAULT); } + + AIMaskedMotion::onDeactivate(); } // End diff --git a/indra/llcharacter/llheadrotmotion.h b/indra/llcharacter/llheadrotmotion.h index 97e61eac1..5fa4610bf 100644 --- a/indra/llcharacter/llheadrotmotion.h +++ b/indra/llcharacter/llheadrotmotion.h @@ -46,11 +46,11 @@ // class LLHeadRotMotion //----------------------------------------------------------------------------- class LLHeadRotMotion : - public LLMotion + public AIMaskedMotion { public: // Constructor - LLHeadRotMotion(const LLUUID &id); + LLHeadRotMotion(LLUUID const& id, LLMotionController* controller); // Destructor virtual ~LLHeadRotMotion(); @@ -62,7 +62,7 @@ public: // static constructor // all subclasses must implement such a function and register it - static LLMotion *create(const LLUUID &id) { return new LLHeadRotMotion(id); } + static LLMotion* create(LLUUID const& id, LLMotionController* controller) { return new LLHeadRotMotion(id, controller); } public: //------------------------------------------------------------------------- @@ -94,19 +94,11 @@ public: // must return true to indicate success and be available for activation virtual LLMotionInitStatus onInitialize(LLCharacter *character); - // called when a motion is activated - // must return TRUE to indicate success, or else - // it will be deactivated - virtual BOOL onActivate(); - // called per time step // must return TRUE while it is active, and // must return FALSE when the motion is completed. virtual BOOL onUpdate(F32 time, U8* joint_mask); - // called when a motion is deactivated - virtual void onDeactivate(); - public: //------------------------------------------------------------------------- // joint states to be animated @@ -129,11 +121,11 @@ public: // class LLEyeMotion //----------------------------------------------------------------------------- class LLEyeMotion : - public LLMotion + public AIMaskedMotion { public: // Constructor - LLEyeMotion(const LLUUID &id); + LLEyeMotion(LLUUID const& id, LLMotionController* controller); // Destructor virtual ~LLEyeMotion(); @@ -145,7 +137,7 @@ public: // static constructor // all subclasses must implement such a function and register it - static LLMotion *create( const LLUUID &id) { return new LLEyeMotion(id); } + static LLMotion* create(LLUUID const& id, LLMotionController* controller) { return new LLEyeMotion(id, controller); } public: //------------------------------------------------------------------------- @@ -177,11 +169,6 @@ public: // must return true to indicate success and be available for activation virtual LLMotionInitStatus onInitialize(LLCharacter *character); - // called when a motion is activated - // must return TRUE to indicate success, or else - // it will be deactivated - virtual BOOL onActivate(); - // called per time step // must return TRUE while it is active, and // must return FALSE when the motion is completed. diff --git a/indra/llcharacter/lljoint.h b/indra/llcharacter/lljoint.h index fe14c1e44..cb55e6ca7 100644 --- a/indra/llcharacter/lljoint.h +++ b/indra/llcharacter/lljoint.h @@ -41,7 +41,7 @@ #include "lldarray.h" const S32 LL_CHARACTER_MAX_JOINTS_PER_MESH = 15; -const U32 LL_CHARACTER_MAX_JOINTS = 32; // must be divisible by 4! +const U32 LL_CHARACTER_MAX_JOINTS = 32; // must be divisible by 16! const U32 LL_HAND_JOINT_NUM = 31; const U32 LL_FACE_JOINT_NUM = 30; const S32 LL_CHARACTER_MAX_PRIORITY = 7; diff --git a/indra/llcharacter/llkeyframefallmotion.cpp b/indra/llcharacter/llkeyframefallmotion.cpp index 15ad1b9e9..336d79e65 100644 --- a/indra/llcharacter/llkeyframefallmotion.cpp +++ b/indra/llcharacter/llkeyframefallmotion.cpp @@ -49,7 +49,7 @@ // LLKeyframeFallMotion() // Class Constructor //----------------------------------------------------------------------------- -LLKeyframeFallMotion::LLKeyframeFallMotion(const LLUUID &id) : LLKeyframeMotion(id) +LLKeyframeFallMotion::LLKeyframeFallMotion(LLUUID const& id, LLMotionController* controller) : LLKeyframeMotion(id, controller) { mVelocityZ = 0.f; mCharacter = NULL; diff --git a/indra/llcharacter/llkeyframefallmotion.h b/indra/llcharacter/llkeyframefallmotion.h index 495be977f..0d2553ce3 100644 --- a/indra/llcharacter/llkeyframefallmotion.h +++ b/indra/llcharacter/llkeyframefallmotion.h @@ -47,7 +47,7 @@ class LLKeyframeFallMotion : { public: // Constructor - LLKeyframeFallMotion(const LLUUID &id); + LLKeyframeFallMotion(LLUUID const& id, LLMotionController* controller); // Destructor virtual ~LLKeyframeFallMotion(); @@ -59,7 +59,7 @@ public: // static constructor // all subclasses must implement such a function and register it - static LLMotion *create(const LLUUID &id) { return new LLKeyframeFallMotion(id); } + static LLMotion* create(LLUUID const& id, LLMotionController* controller) { return new LLKeyframeFallMotion(id, controller); } public: //------------------------------------------------------------------------- diff --git a/indra/llcharacter/llkeyframemotion.cpp b/indra/llcharacter/llkeyframemotion.cpp index 98906d3d4..711ac21e5 100644 --- a/indra/llcharacter/llkeyframemotion.cpp +++ b/indra/llcharacter/llkeyframemotion.cpp @@ -84,38 +84,55 @@ LLKeyframeMotion::JointMotionList::~JointMotionList() for_each(mJointMotionArray.begin(), mJointMotionArray.end(), DeletePointer()); } -U32 LLKeyframeMotion::JointMotionList::dumpDiagInfo() +//Singu: add parameter 'silent'. +U32 LLKeyframeMotion::JointMotionList::dumpDiagInfo(bool silent) const { S32 total_size = sizeof(JointMotionList); for (U32 i = 0; i < getNumJointMotions(); i++) { - LLKeyframeMotion::JointMotion* joint_motion_p = mJointMotionArray[i]; + LLKeyframeMotion::JointMotion const* joint_motion_p = mJointMotionArray[i]; - llinfos << "\tJoint " << joint_motion_p->mJointName << llendl; + if (!silent) + { + llinfos << "\tJoint " << joint_motion_p->mJointName << llendl; + } if (joint_motion_p->mUsage & LLJointState::SCALE) { - llinfos << "\t" << joint_motion_p->mScaleCurve.mNumKeys << " scale keys at " - << joint_motion_p->mScaleCurve.mNumKeys * sizeof(ScaleKey) << " bytes" << llendl; - + if (!silent) + { + llinfos << "\t" << joint_motion_p->mScaleCurve.mNumKeys << " scale keys at " + << joint_motion_p->mScaleCurve.mNumKeys * sizeof(ScaleKey) << " bytes" << llendl; + } total_size += joint_motion_p->mScaleCurve.mNumKeys * sizeof(ScaleKey); } if (joint_motion_p->mUsage & LLJointState::ROT) { - llinfos << "\t" << joint_motion_p->mRotationCurve.mNumKeys << " rotation keys at " - << joint_motion_p->mRotationCurve.mNumKeys * sizeof(RotationKey) << " bytes" << llendl; - + if (!silent) + { + llinfos << "\t" << joint_motion_p->mRotationCurve.mNumKeys << " rotation keys at " + << joint_motion_p->mRotationCurve.mNumKeys * sizeof(RotationKey) << " bytes" << llendl; + } total_size += joint_motion_p->mRotationCurve.mNumKeys * sizeof(RotationKey); } if (joint_motion_p->mUsage & LLJointState::POS) { - llinfos << "\t" << joint_motion_p->mPositionCurve.mNumKeys << " position keys at " - << joint_motion_p->mPositionCurve.mNumKeys * sizeof(PositionKey) << " bytes" << llendl; - + if (!silent) + { + llinfos << "\t" << joint_motion_p->mPositionCurve.mNumKeys << " position keys at " + << joint_motion_p->mPositionCurve.mNumKeys * sizeof(PositionKey) << " bytes" << llendl; + } total_size += joint_motion_p->mPositionCurve.mNumKeys * sizeof(PositionKey); } } - llinfos << "Size: " << total_size << " bytes" << llendl; + //Singu: Also add memory used by the constraints. + S32 constraints_size = mConstraints.size() * sizeof(constraint_list_t::value_type); + total_size += constraints_size; + if (!silent) + { + llinfos << "\t" << mConstraints.size() << " constraints at " << constraints_size << " bytes" << llendl; + llinfos << "Size: " << total_size << " bytes" << llendl; + } return total_size; } @@ -427,9 +444,8 @@ void LLKeyframeMotion::JointMotion::update(LLJointState* joint_state, F32 time, // LLKeyframeMotion() // Class Constructor //----------------------------------------------------------------------------- -LLKeyframeMotion::LLKeyframeMotion(const LLUUID &id) - : LLMotion(id), - mJointMotionList(NULL), +LLKeyframeMotion::LLKeyframeMotion(const LLUUID &id, LLMotionController* controller) + : LLMotion(id, controller), mPelvisp(NULL), mLastSkeletonSerialNum(0), mLastUpdateTime(0.f), @@ -452,9 +468,9 @@ LLKeyframeMotion::~LLKeyframeMotion() //----------------------------------------------------------------------------- // create() //----------------------------------------------------------------------------- -LLMotion *LLKeyframeMotion::create(const LLUUID &id) +LLMotion* LLKeyframeMotion::create(LLUUID const& id, LLMotionController* controller) { - return new LLKeyframeMotion(id); + return new LLKeyframeMotion(id, controller); } //----------------------------------------------------------------------------- @@ -517,6 +533,7 @@ LLMotion::LLMotionInitStatus LLKeyframeMotion::onInitialize(LLCharacter *charact case ASSET_FETCHED: return STATUS_HOLD; case ASSET_FETCH_FAILED: + llwarns << "Trying to initialize a motion that failed to be fetched." << llendl; return STATUS_FAILURE; case ASSET_LOADED: return STATUS_SUCCESS; @@ -526,7 +543,7 @@ LLMotion::LLMotionInitStatus LLKeyframeMotion::onInitialize(LLCharacter *charact break; } - LLKeyframeMotion::JointMotionList* joint_motion_list = LLKeyframeDataCache::getKeyframeData(getID()); + LLKeyframeMotion::JointMotionListPtr joint_motion_list = LLKeyframeDataCache::getKeyframeData(getID()); if(joint_motion_list) { @@ -801,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); @@ -819,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()); } @@ -1227,13 +1283,42 @@ void LLKeyframeMotion::applyConstraint(JointConstraint* constraint, F32 time, U8 } } +// Helper class. +template +struct AIAutoDestruct +{ + T* mPtr; + AIAutoDestruct() : mPtr(NULL) { } + ~AIAutoDestruct() { delete mPtr; } + void add(T* ptr) { mPtr = ptr; } +}; + //----------------------------------------------------------------------------- // deserialize() //----------------------------------------------------------------------------- BOOL LLKeyframeMotion::deserialize(LLDataPacker& dp) { BOOL old_version = FALSE; - mJointMotionList = new LLKeyframeMotion::JointMotionList; + + // + + // First add a new LLKeyframeMotion::JointMotionList to the cache, then assign a pointer + // to that to mJointMotionList. In LLs code the cache is never deleted again. Now it is + // is deleted when the last mJointMotionList pointer is destructed. + // + // It is possible that we get here for an already added animation, because animations can + // be requested multiple times (we get here from LLKeyframeMotion::onLoadComplete) when + // the animation was still downloading from a previous request for another LLMotionController + // object (avatar). In that case we just overwrite the old data while decoding it again. + mJointMotionList = LLKeyframeDataCache::getKeyframeData(getID()); + bool singu_new_joint_motion_list = !mJointMotionList; + if (singu_new_joint_motion_list) + { + // Create a new JointMotionList. + mJointMotionList = LLKeyframeDataCache::createKeyframeData(getID()); + } + + // //------------------------------------------------------------------------- // get base priority @@ -1396,8 +1481,10 @@ BOOL LLKeyframeMotion::deserialize(LLDataPacker& dp) return FALSE; } - mJointMotionList->mJointMotionArray.clear(); - mJointMotionList->mJointMotionArray.reserve(num_motions); + if (singu_new_joint_motion_list) + { + mJointMotionList->mJointMotionArray.reserve(num_motions); + } mJointStates.clear(); mJointStates.reserve(num_motions); @@ -1407,8 +1494,19 @@ BOOL LLKeyframeMotion::deserialize(LLDataPacker& dp) for(U32 i=0; i watcher; + JointMotion* joint_motion = new JointMotion; - mJointMotionList->mJointMotionArray.push_back(joint_motion); + if (singu_new_joint_motion_list) + { + // Pass ownership to mJointMotionList. + mJointMotionList->mJointMotionArray.push_back(joint_motion); + } + else + { + // Just delete this at the end. + watcher.add(joint_motion); + } std::string joint_name; if (!dp.unpackString(joint_name, "joint_name")) @@ -1836,7 +1934,15 @@ BOOL LLKeyframeMotion::deserialize(LLDataPacker& dp) return FALSE; } - mJointMotionList->mConstraints.push_front(constraintp); + AIAutoDestruct watcher; + if (singu_new_joint_motion_list) + { + mJointMotionList->mConstraints.push_front(constraintp); + } + else + { + watcher.add(constraintp); + } constraintp->mJointStateIndices = new S32[constraintp->mChainLength + 1]; // note: mChainLength is size-limited - comes from a byte @@ -1876,15 +1982,12 @@ BOOL LLKeyframeMotion::deserialize(LLDataPacker& dp) if (constraintp->mJointStateIndices[i] < 0 ) { llwarns << "No joint index for constraint " << i << llendl; - delete constraintp; return FALSE; } } } } - // *FIX: support cleanup of old keyframe data - LLKeyframeDataCache::addKeyframeData(getID(), mJointMotionList); mAssetStatus = ASSET_LOADED; setupPose(); @@ -2226,16 +2329,24 @@ void LLKeyframeMotion::onLoadComplete(LLVFS *vfs, //-------------------------------------------------------------------- // LLKeyframeDataCache::dumpDiagInfo() //-------------------------------------------------------------------- -void LLKeyframeDataCache::dumpDiagInfo() +// +// quiet = 0 : print everything in detail. +// 1 : print the UUIDs of all animations in the cache and the total memory usage. +// 2 : only print the total memory usage. +// +void LLKeyframeDataCache::dumpDiagInfo(int quiet) { // keep track of totals U32 total_size = 0; char buf[1024]; /* Flawfinder: ignore */ - llinfos << "-----------------------------------------------------" << llendl; - llinfos << " Global Motion Table (DEBUG only)" << llendl; - llinfos << "-----------------------------------------------------" << llendl; + if (quiet < 2) + { + llinfos << "-----------------------------------------------------" << llendl; + llinfos << " Global Motion Table" << llendl; + llinfos << "-----------------------------------------------------" << llendl; + } // print each loaded mesh, and it's memory usage for (keyframe_data_map_t::iterator map_it = sKeyframeDataMap.begin(); @@ -2243,30 +2354,46 @@ void LLKeyframeDataCache::dumpDiagInfo() { U32 joint_motion_kb; - LLKeyframeMotion::JointMotionList *motion_list_p = map_it->second; + LLKeyframeMotion::JointMotionList const* motion_list_p = map_it->get(); - llinfos << "Motion: " << map_it->first << llendl; + if (quiet < 2) + { + llinfos << "Motion: " << map_it->key() << llendl; + } - joint_motion_kb = motion_list_p->dumpDiagInfo(); - - total_size += joint_motion_kb; + if (motion_list_p) + { + joint_motion_kb = motion_list_p->dumpDiagInfo(quiet); + total_size += joint_motion_kb; + } } - llinfos << "-----------------------------------------------------" << llendl; + if (quiet < 2) + { + llinfos << "-----------------------------------------------------" << llendl; + } llinfos << "Motions\tTotal Size" << llendl; snprintf(buf, sizeof(buf), "%d\t\t%d bytes", (S32)sKeyframeDataMap.size(), total_size ); /* Flawfinder: ignore */ llinfos << buf << llendl; - llinfos << "-----------------------------------------------------" << llendl; + if (quiet < 2) + { + llinfos << "-----------------------------------------------------" << llendl; + } } //-------------------------------------------------------------------- -// LLKeyframeDataCache::addKeyframeData() +// LLKeyframeDataCache::createKeyframeData() //-------------------------------------------------------------------- -void LLKeyframeDataCache::addKeyframeData(const LLUUID& id, LLKeyframeMotion::JointMotionList* joint_motion_listp) +// This function replaces LLKeyframeDataCache::addKeyframeData and was rewritten to fix a memory leak (aka, the usage of AICachedPointer). +LLKeyframeMotion::JointMotionListPtr LLKeyframeDataCache::createKeyframeData(LLUUID const& id) { - sKeyframeDataMap[id] = joint_motion_listp; + std::pair result = + sKeyframeDataMap.insert(AICachedPointer(id, new LLKeyframeMotion::JointMotionList, &sKeyframeDataMap)); + llassert(result.second); // id may not already exist in the cache. + return &*result.first; // Construct and return a JointMotionListPt from a pointer to the actually inserted AICachedPointer. } +// //-------------------------------------------------------------------- // LLKeyframeDataCache::removeKeyframeData() @@ -2276,7 +2403,6 @@ void LLKeyframeDataCache::removeKeyframeData(const LLUUID& id) keyframe_data_map_t::iterator found_data = sKeyframeDataMap.find(id); if (found_data != sKeyframeDataMap.end()) { - delete found_data->second; sKeyframeDataMap.erase(found_data); } } @@ -2284,14 +2410,14 @@ void LLKeyframeDataCache::removeKeyframeData(const LLUUID& id) //-------------------------------------------------------------------- // LLKeyframeDataCache::getKeyframeData() //-------------------------------------------------------------------- -LLKeyframeMotion::JointMotionList* LLKeyframeDataCache::getKeyframeData(const LLUUID& id) +LLKeyframeMotion::JointMotionListPtr LLKeyframeDataCache::getKeyframeData(const LLUUID& id) { keyframe_data_map_t::iterator found_data = sKeyframeDataMap.find(id); if (found_data == sKeyframeDataMap.end()) { return NULL; } - return found_data->second; + return &*found_data; // Construct and return a JointMotionListPt from a pointer to the found AICachedPointer. } //-------------------------------------------------------------------- @@ -2307,7 +2433,6 @@ LLKeyframeDataCache::~LLKeyframeDataCache() //----------------------------------------------------------------------------- void LLKeyframeDataCache::clear() { - for_each(sKeyframeDataMap.begin(), sKeyframeDataMap.end(), DeletePairedPointer()); sKeyframeDataMap.clear(); } diff --git a/indra/llcharacter/llkeyframemotion.h b/indra/llcharacter/llkeyframemotion.h index 50d9d0504..4c54d5500 100644 --- a/indra/llcharacter/llkeyframemotion.h +++ b/indra/llcharacter/llkeyframemotion.h @@ -5,6 +5,7 @@ * $LicenseInfo:firstyear=2001&license=viewergpl$ * * Copyright (c) 2001-2009, Linden Research, Inc. + * AICachedPointer and AICachedPointPtr copyright (c) 2013, Aleric Inglewood. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab @@ -48,10 +49,12 @@ #include "v3dmath.h" #include "v3math.h" #include "llbvhconsts.h" +#include class LLKeyframeDataCache; class LLVFS; class LLDataPacker; +class LLMotionController; #define MIN_REQUIRED_PIXEL_AREA_KEYFRAME (40.f) #define MAX_CHAIN_LENGTH (4) @@ -59,6 +62,112 @@ class LLDataPacker; const S32 KEYFRAME_MOTION_VERSION = 1; const S32 KEYFRAME_MOTION_SUBVERSION = 0; +//----------------------------------------------------------------------------- +// + +template +class AICachedPointer; + +template +void intrusive_ptr_add_ref(AICachedPointer const* p); + +template +void intrusive_ptr_release(AICachedPointer const* p); + +template +class AICachedPointer +{ + public: + typedef std::set > container_type; + + private: + KEY mKey; // The unique key. + LLPointer mData; // The actual data pointer. + container_type* mCache; // Pointer to the underlaying cache. + mutable int mRefCount; // Number of AICachedPointerPtr's pointing to this object. + + public: + // Construct a NULL pointer. This is needed when adding a new entry to a std::set, it is always first default constructed. + AICachedPointer(void) : mCache(NULL) { } + + // Copy constructor. This is needed to replace the std::set inserted instance with its actual value. + AICachedPointer(AICachedPointer const& cptr) : mKey(cptr.mKey), mData(cptr.mData), mCache(cptr.mCache), mRefCount(0) { } + + // Construct a AICachedPointer that points to 'ptr' with key 'key'. + AICachedPointer(KEY const& key, T* ptr, container_type* cache) : mKey(key), mData(ptr), mCache(cache), mRefCount(-1) { } + + // Construct a temporary NULL pointer that can be used in a search for a key. + AICachedPointer(KEY const& key) : mKey(key), mCache(NULL) { } + + // Accessors for key and data. + KEY const& key(void) const { return mKey; } + T const* get(void) const { return mData.get(); } + T* get(void) { return mData.get(); } + + // Order only by key. + friend bool operator<(AICachedPointer const& cp1, AICachedPointer const& cp2) { return cp1.mKey < cp2.mKey; } + + private: + friend void intrusive_ptr_add_ref<>(AICachedPointer const* p); + friend void intrusive_ptr_release<>(AICachedPointer const* p); + + private: + AICachedPointer& operator=(AICachedPointer const&); +}; + +template +void intrusive_ptr_add_ref(AICachedPointer const* p) +{ + llassert(p->mCache); + if (p->mCache) + { + p->mRefCount++; + } +} + +template +void intrusive_ptr_release(AICachedPointer const* p) +{ + llassert(p->mCache); + if (p->mCache) + { + if (--p->mRefCount == 0) + { + p->mCache->erase(p->mKey); + } + } +} + +template +class AICachedPointerPtr +{ + private: + boost::intrusive_ptr const> mPtr; + static int sCnt; + + public: + AICachedPointerPtr(void) { ++sCnt; } + AICachedPointerPtr(AICachedPointerPtr const& cpp) : mPtr(cpp.mPtr) { ++sCnt; } + AICachedPointerPtr(AICachedPointer const* cp) : mPtr(cp) { ++sCnt; } + ~AICachedPointerPtr() { --sCnt; } + + typedef boost::intrusive_ptr const> const AICachedPointerPtr::* const bool_type; + operator bool_type() const { return mPtr ? &AICachedPointerPtr::mPtr : NULL; } + + T const* operator->() const { return mPtr->get(); } + T* operator->() { return const_cast&>(*mPtr).get(); } + T const& operator*() const { return *mPtr->get(); } + T& operator*() { return *const_cast&>(*mPtr).get(); } + + AICachedPointerPtr& operator=(AICachedPointerPtr const& cpp) { mPtr = cpp.mPtr; return *this; } +}; + +template +int AICachedPointerPtr::sCnt; + +// +//----------------------------------------------------------------------------- + //----------------------------------------------------------------------------- // class LLKeyframeMotion //----------------------------------------------------------------------------- @@ -68,7 +177,7 @@ class LLKeyframeMotion : friend class LLKeyframeDataCache; public: // Constructor - LLKeyframeMotion(const LLUUID &id); + LLKeyframeMotion(const LLUUID &id, LLMotionController* controller); // Destructor virtual ~LLKeyframeMotion(); @@ -85,7 +194,7 @@ public: // static constructor // all subclasses must implement such a function and register it - static LLMotion *create(const LLUUID& id); + static LLMotion* create(LLUUID const& id, LLMotionController* controller); public: //------------------------------------------------------------------------- @@ -158,7 +267,7 @@ public: U32 getFileSize(); BOOL serialize(LLDataPacker& dp) const; BOOL deserialize(LLDataPacker& dp); - BOOL isLoaded() { return mJointMotionList != NULL; } + BOOL isLoaded() { return !!mJointMotionList; } // setters for modifying a keyframe animation @@ -393,7 +502,7 @@ public: //------------------------------------------------------------------------- // JointMotionList //------------------------------------------------------------------------- - class JointMotionList + class JointMotionList : public LLRefCount { public: std::vector mJointMotionArray; @@ -416,19 +525,22 @@ public: public: JointMotionList(); ~JointMotionList(); - U32 dumpDiagInfo(); + U32 dumpDiagInfo(bool silent = false) const; JointMotion* getJointMotion(U32 index) const { llassert(index < mJointMotionArray.size()); return mJointMotionArray[index]; } U32 getNumJointMotions() const { return mJointMotionArray.size(); } }; + // Singu: Type of a pointer to the cached pointer (in LLKeyframeDataCache::sKeyframeDataMap) to a JointMotionList object. + typedef AICachedPointerPtr JointMotionListPtr; + protected: static LLVFS* sVFS; //------------------------------------------------------------------------- // Member Data //------------------------------------------------------------------------- - JointMotionList* mJointMotionList; + JointMotionListPtr mJointMotionList; // singu: automatically clean up cache entry when destructed. std::vector > mJointStates; LLJoint* mPelvisp; LLCharacter* mCharacter; @@ -442,21 +554,24 @@ protected: class LLKeyframeDataCache { -public: - // *FIX: implement this as an actual singleton member of LLKeyframeMotion +private: + friend class LLKeyframeMotion; LLKeyframeDataCache(){}; ~LLKeyframeDataCache(); - typedef std::map keyframe_data_map_t; +public: + typedef AICachedPointer::container_type keyframe_data_map_t; // singu: add automatic cache cleanup. static keyframe_data_map_t sKeyframeDataMap; - static void addKeyframeData(const LLUUID& id, LLKeyframeMotion::JointMotionList*); - static LLKeyframeMotion::JointMotionList* getKeyframeData(const LLUUID& id); + // + static LLKeyframeMotion::JointMotionListPtr createKeyframeData(LLUUID const& id); // id may not exist. + static LLKeyframeMotion::JointMotionListPtr getKeyframeData(LLUUID const& id); // id may or may not exists. Returns a NULL pointer when it doesn't exist. + // static void removeKeyframeData(const LLUUID& id); //print out diagnostic info - static void dumpDiagInfo(); + static void dumpDiagInfo(int quiet = 0); // singu: added param 'quiet'. static void clear(); }; diff --git a/indra/llcharacter/llkeyframemotionparam.cpp b/indra/llcharacter/llkeyframemotionparam.cpp deleted file mode 100644 index 4df1ef46b..000000000 --- a/indra/llcharacter/llkeyframemotionparam.cpp +++ /dev/null @@ -1,456 +0,0 @@ -/** - * @file llkeyframemotionparam.cpp - * @brief Implementation of LLKeyframeMotion class. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -//----------------------------------------------------------------------------- -// Header Files -//----------------------------------------------------------------------------- -#include "linden_common.h" - -#include "llkeyframemotionparam.h" -#include "llcharacter.h" -#include "llmath.h" -#include "m3math.h" -#include "lldir.h" -#include "llanimationstates.h" - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -// LLKeyframeMotionParam class -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -//----------------------------------------------------------------------------- -// LLKeyframeMotionParam() -// Class Constructor -//----------------------------------------------------------------------------- -LLKeyframeMotionParam::LLKeyframeMotionParam( const LLUUID &id) : LLMotion(id) -{ - mDefaultKeyframeMotion = NULL; - mCharacter = NULL; - - mEaseInDuration = 0.f; - mEaseOutDuration = 0.f; - mDuration = 0.f; - mPriority = LLJoint::LOW_PRIORITY; -} - - -//----------------------------------------------------------------------------- -// ~LLKeyframeMotionParam() -// Class Destructor -//----------------------------------------------------------------------------- -LLKeyframeMotionParam::~LLKeyframeMotionParam() -{ - for (motion_map_t::iterator iter = mParameterizedMotions.begin(); - iter != mParameterizedMotions.end(); ++iter) - { - motion_list_t& motionList = iter->second; - for (motion_list_t::iterator iter2 = motionList.begin(); iter2 != motionList.end(); ++iter2) - { - const ParameterizedMotion& paramMotion = *iter2; - delete paramMotion.mMotion; - } - motionList.clear(); - } - mParameterizedMotions.clear(); -} - -//----------------------------------------------------------------------------- -// LLKeyframeMotionParam::onInitialize(LLCharacter *character) -//----------------------------------------------------------------------------- -LLMotion::LLMotionInitStatus LLKeyframeMotionParam::onInitialize(LLCharacter *character) -{ - mCharacter = character; - - if (!loadMotions()) - { - return STATUS_FAILURE; - } - - for (motion_map_t::iterator iter = mParameterizedMotions.begin(); - iter != mParameterizedMotions.end(); ++iter) - { - motion_list_t& motionList = iter->second; - for (motion_list_t::iterator iter2 = motionList.begin(); iter2 != motionList.end(); ++iter2) - { - const ParameterizedMotion& paramMotion = *iter2; - LLMotion* motion = paramMotion.mMotion; - motion->onInitialize(character); - - if (motion->getDuration() > mEaseInDuration) - { - mEaseInDuration = motion->getEaseInDuration(); - } - - if (motion->getEaseOutDuration() > mEaseOutDuration) - { - mEaseOutDuration = motion->getEaseOutDuration(); - } - - if (motion->getDuration() > mDuration) - { - mDuration = motion->getDuration(); - } - - if (motion->getPriority() > mPriority) - { - mPriority = motion->getPriority(); - } - - LLPose *pose = motion->getPose(); - - mPoseBlender.addMotion(motion); - for (LLJointState *jsp = pose->getFirstJointState(); jsp; jsp = pose->getNextJointState()) - { - LLPose *blendedPose = mPoseBlender.getBlendedPose(); - blendedPose->addJointState(jsp); - } - } - } - - return STATUS_SUCCESS; -} - -//----------------------------------------------------------------------------- -// LLKeyframeMotionParam::onActivate() -//----------------------------------------------------------------------------- -BOOL LLKeyframeMotionParam::onActivate() -{ - for (motion_map_t::iterator iter = mParameterizedMotions.begin(); - iter != mParameterizedMotions.end(); ++iter) - { - motion_list_t& motionList = iter->second; - for (motion_list_t::iterator iter2 = motionList.begin(); iter2 != motionList.end(); ++iter2) - { - const ParameterizedMotion& paramMotion = *iter2; - paramMotion.mMotion->activate(mActivationTimestamp); - } - } - return TRUE; -} - - -//----------------------------------------------------------------------------- -// LLKeyframeMotionParam::onUpdate() -//----------------------------------------------------------------------------- -BOOL LLKeyframeMotionParam::onUpdate(F32 time, U8* joint_mask) -{ - F32 weightFactor = 1.f / (F32)mParameterizedMotions.size(); - - // zero out all pose weights - for (motion_map_t::iterator iter = mParameterizedMotions.begin(); - iter != mParameterizedMotions.end(); ++iter) - { - motion_list_t& motionList = iter->second; - for (motion_list_t::iterator iter2 = motionList.begin(); iter2 != motionList.end(); ++iter2) - { - const ParameterizedMotion& paramMotion = *iter2; -// llinfos << "Weight for pose " << paramMotion.mMotion->getName() << " is " << paramMotion.mMotion->getPose()->getWeight() << llendl; - paramMotion.mMotion->getPose()->setWeight(0.f); - } - } - - - for (motion_map_t::iterator iter = mParameterizedMotions.begin(); - iter != mParameterizedMotions.end(); ++iter) - { - const std::string& paramName = iter->first; - F32* paramValue = (F32 *)mCharacter->getAnimationData(paramName); - if (NULL == paramValue) // unexpected, but... - { - llwarns << "paramValue == NULL" << llendl; - continue; - } - - // DANGER! Do not modify mParameterizedMotions while using these pointers! - const ParameterizedMotion* firstMotion = NULL; - const ParameterizedMotion* secondMotion = NULL; - - motion_list_t& motionList = iter->second; - for (motion_list_t::iterator iter2 = motionList.begin(); iter2 != motionList.end(); ++iter2) - { - const ParameterizedMotion& paramMotion = *iter2; - paramMotion.mMotion->onUpdate(time, joint_mask); - - F32 distToParam = paramMotion.mParam - *paramValue; - - if ( distToParam <= 0.f) - { - // keep track of the motion closest to the parameter value - firstMotion = ¶mMotion; - } - else - { - // we've passed the parameter value - // so store the first motion we find as the second one we want to blend... - if (firstMotion && !secondMotion ) - { - secondMotion = ¶mMotion; - } - //...or, if we've seen no other motion so far, make sure we blend to this only - else if (!firstMotion) - { - firstMotion = ¶mMotion; - secondMotion = ¶mMotion; - } - } - } - - LLPose *firstPose; - LLPose *secondPose; - - if (firstMotion) - firstPose = firstMotion->mMotion->getPose(); - else - firstPose = NULL; - - if (secondMotion) - secondPose = secondMotion->mMotion->getPose(); - else - secondPose = NULL; - - // now modify weight of the subanim (only if we are blending between two motions) - if (firstMotion && secondMotion) - { - if (firstMotion == secondMotion) - { - firstPose->setWeight(weightFactor); - } - else if (firstMotion->mParam == secondMotion->mParam) - { - firstPose->setWeight(0.5f * weightFactor); - secondPose->setWeight(0.5f * weightFactor); - } - else - { - F32 first_weight = 1.f - - ((llclamp(*paramValue - firstMotion->mParam, 0.f, (secondMotion->mParam - firstMotion->mParam))) / - (secondMotion->mParam - firstMotion->mParam)); - first_weight = llclamp(first_weight, 0.f, 1.f); - - F32 second_weight = 1.f - first_weight; - - firstPose->setWeight(first_weight * weightFactor); - secondPose->setWeight(second_weight * weightFactor); - -// llinfos << "Parameter " << *paramName << ": " << *paramValue << llendl; -// llinfos << "Weights " << firstPose->getWeight() << " " << secondPose->getWeight() << llendl; - } - } - else if (firstMotion && !secondMotion) - { - firstPose->setWeight(weightFactor); - } - } - - // blend poses - mPoseBlender.blendAndApply(); - - llinfos << "Param Motion weight " << mPoseBlender.getBlendedPose()->getWeight() << llendl; - - return TRUE; -} - -//----------------------------------------------------------------------------- -// LLKeyframeMotionParam::onDeactivate() -//----------------------------------------------------------------------------- -void LLKeyframeMotionParam::onDeactivate() -{ - for (motion_map_t::iterator iter = mParameterizedMotions.begin(); - iter != mParameterizedMotions.end(); ++iter) - { - motion_list_t& motionList = iter->second; - for (motion_list_t::iterator iter2 = motionList.begin(); iter2 != motionList.end(); ++iter2) - { - const ParameterizedMotion& paramMotion = *iter2; - paramMotion.mMotion->onDeactivate(); - } - } -} - -//----------------------------------------------------------------------------- -// LLKeyframeMotionParam::addKeyframeMotion() -//----------------------------------------------------------------------------- -BOOL LLKeyframeMotionParam::addKeyframeMotion(char *name, const LLUUID &id, char *param, F32 value) -{ - LLMotion *newMotion = mCharacter->createMotion( id ); - - if (!newMotion) - { - return FALSE; - } - - newMotion->setName(name); - - // now add motion to this list - mParameterizedMotions[param].insert(ParameterizedMotion(newMotion, value)); - - return TRUE; -} - - -//----------------------------------------------------------------------------- -// LLKeyframeMotionParam::setDefaultKeyframeMotion() -//----------------------------------------------------------------------------- -void LLKeyframeMotionParam::setDefaultKeyframeMotion(char *name) -{ - for (motion_map_t::iterator iter = mParameterizedMotions.begin(); - iter != mParameterizedMotions.end(); ++iter) - { - motion_list_t& motionList = iter->second; - for (motion_list_t::iterator iter2 = motionList.begin(); iter2 != motionList.end(); ++iter2) - { - const ParameterizedMotion& paramMotion = *iter2; - if (paramMotion.mMotion->getName() == name) - { - mDefaultKeyframeMotion = paramMotion.mMotion; - } - } - } -} - -//----------------------------------------------------------------------------- -// loadMotions() -//----------------------------------------------------------------------------- -BOOL LLKeyframeMotionParam::loadMotions() -{ - //------------------------------------------------------------------------- - // Load named file by concatenating the character prefix with the motion name. - // Load data into a buffer to be parsed. - //------------------------------------------------------------------------- - //std::string path = gDirUtilp->getExpandedFilename(LL_PATH_MOTIONS,mCharacter->getAnimationPrefix()) - // + "_" + getName() + ".llp"; - //RN: deprecated unused reference to "motion" directory - std::string path; - - - //------------------------------------------------------------------------- - // open the file - //------------------------------------------------------------------------- - S32 fileSize = 0; - LLAPRFile infile(path, LL_APR_R, &fileSize); - apr_file_t* fp = infile.getFileHandle() ; - if (!fp || fileSize == 0) - { - llinfos << "ERROR: can't open: " << path << llendl; - return FALSE; - } - - // allocate a text buffer - try - { - std::vector text(fileSize+1); - - //------------------------------------------------------------------------- - // load data from file into buffer - //------------------------------------------------------------------------- - bool error = false; - char *p = &text[0]; - while ( 1 ) - { - if (apr_file_eof(fp) == APR_EOF) - { - break; - } - if (apr_file_gets(p, 1024, fp) != APR_SUCCESS) - { - error = true; - break; - } - while ( *(++p) ) - ; - } - - //------------------------------------------------------------------------- - // close the file - //------------------------------------------------------------------------- - infile.close(); - - //------------------------------------------------------------------------- - // check for error - //------------------------------------------------------------------------- - llassert( p <= (&text[0] + fileSize) ); - - if ( error ) - { - llinfos << "ERROR: error while reading from " << path << llendl; - return FALSE; - } - - llinfos << "Loading parametric keyframe data for: " << getName() << llendl; - - //------------------------------------------------------------------------- - // parse the text and build keyframe data structures - //------------------------------------------------------------------------- - p = &text[0]; - S32 num; - char strA[80]; /* Flawfinder: ignore */ - char strB[80]; /* Flawfinder: ignore */ - F32 floatA = 0.0f; - - - //------------------------------------------------------------------------- - // get priority - //------------------------------------------------------------------------- - BOOL isFirstMotion = TRUE; - num = sscanf(p, "%79s %79s %f", strA, strB, &floatA); /* Flawfinder: ignore */ - - while(1) - { - if (num == 0 || num == EOF) break; - if ((num != 3)) - { - llinfos << "WARNING: can't read parametric motion" << llendl; - return FALSE; - } - - addKeyframeMotion(strA, gAnimLibrary.stringToAnimState(std::string(strA)), strB, floatA); - if (isFirstMotion) - { - isFirstMotion = FALSE; - setDefaultKeyframeMotion(strA); - } - - p = strstr(p, "\n"); - if (!p) - { - break; - } - - p++; - num = sscanf(p, "%79s %79s %f", strA, strB, &floatA); /* Flawfinder: ignore */ - } - - return TRUE; - } - catch(std::bad_alloc) - { - llinfos << "ERROR: Unable to allocate keyframe text buffer." << llendl; - return FALSE; - } -} - -// End diff --git a/indra/llcharacter/llkeyframemotionparam.h b/indra/llcharacter/llkeyframemotionparam.h deleted file mode 100644 index f74aea276..000000000 --- a/indra/llcharacter/llkeyframemotionparam.h +++ /dev/null @@ -1,176 +0,0 @@ -/** - * @file llkeyframemotionparam.h - * @brief Implementation of LLKeframeMotionParam class. - * - * $LicenseInfo:firstyear=2002&license=viewergpl$ - * - * Copyright (c) 2002-2009, Linden Research, Inc. - * - * Second Life Viewer Source Code - * The source code in this file ("Source Code") is provided by Linden Lab - * to you under the terms of the GNU General Public License, version 2.0 - * ("GPL"), unless you have obtained a separate licensing agreement - * ("Other License"), formally executed by you and Linden Lab. Terms of - * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 - * - * There are special exceptions to the terms and conditions of the GPL as - * it is applied to this Source Code. View the full text of the exception - * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception - * - * By copying, modifying or distributing this software, you acknowledge - * that you have read and understood your obligations described above, - * and agree to abide by those obligations. - * - * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO - * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, - * COMPLETENESS OR PERFORMANCE. - * $/LicenseInfo$ - */ - -#ifndef LL_LLKEYFRAMEMOTIONPARAM_H -#define LL_LLKEYFRAMEMOTIONPARAM_H - -//----------------------------------------------------------------------------- -// Header files -//----------------------------------------------------------------------------- - -#include - -#include "llmotion.h" -#include "lljointstate.h" -#include "v3math.h" -#include "llquaternion.h" -#include "linked_lists.h" -#include "llkeyframemotion.h" - -//----------------------------------------------------------------------------- -// class LLKeyframeMotionParam -//----------------------------------------------------------------------------- -class LLKeyframeMotionParam : - public LLMotion -{ -public: - // Constructor - LLKeyframeMotionParam(const LLUUID &id); - - // Destructor - virtual ~LLKeyframeMotionParam(); - -public: - //------------------------------------------------------------------------- - // functions to support MotionController and MotionRegistry - //------------------------------------------------------------------------- - - // static constructor - // all subclasses must implement such a function and register it - static LLMotion *create(const LLUUID &id) { return new LLKeyframeMotionParam(id); } - -public: - //------------------------------------------------------------------------- - // animation callbacks to be implemented by subclasses - //------------------------------------------------------------------------- - - // motions must specify whether or not they loop - virtual BOOL getLoop() { - return TRUE; - } - - // motions must report their total duration - virtual F32 getDuration() { - return mDuration; - } - - // motions must report their "ease in" duration - virtual F32 getEaseInDuration() { - return mEaseInDuration; - } - - // motions must report their "ease out" duration. - virtual F32 getEaseOutDuration() { - return mEaseOutDuration; - } - - // motions must report their priority - virtual LLJoint::JointPriority getPriority() { - return mPriority; - } - - virtual LLMotionBlendType getBlendType() { return NORMAL_BLEND; } - - // called to determine when a motion should be activated/deactivated based on avatar pixel coverage - virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_KEYFRAME; } - - // run-time (post constructor) initialization, - // called after parameters have been set - // must return true to indicate success and be available for activation - virtual LLMotionInitStatus onInitialize(LLCharacter *character); - - // called when a motion is activated - // must return TRUE to indicate success, or else - // it will be deactivated - virtual BOOL onActivate(); - - // called per time step - // must return TRUE while it is active, and - // must return FALSE when the motion is completed. - virtual BOOL onUpdate(F32 time, U8* joint_mask); - - // called when a motion is deactivated - virtual void onDeactivate(); - - virtual LLPose* getPose() { return mPoseBlender.getBlendedPose();} - -protected: - //------------------------------------------------------------------------- - // new functions defined by this subclass - //------------------------------------------------------------------------- - struct ParameterizedMotion - { - ParameterizedMotion(LLMotion* motion, F32 param) : mMotion(motion), mParam(param) {} - LLMotion* mMotion; - F32 mParam; - }; - - // add a motion and associated parameter triplet - BOOL addKeyframeMotion(char *name, const LLUUID &id, char *param, F32 value); - - // set default motion for LOD and retrieving blend constants - void setDefaultKeyframeMotion(char *); - - BOOL loadMotions(); - -protected: - //------------------------------------------------------------------------- - // Member Data - //------------------------------------------------------------------------- - - struct compare_motions - { - bool operator() (const ParameterizedMotion& a, const ParameterizedMotion& b) const - { - if (a.mParam != b.mParam) - return (a.mParam < b.mParam); - else - return a.mMotion < b.mMotion; - } - }; - - typedef std::set < ParameterizedMotion, compare_motions > motion_list_t; - typedef std::map motion_map_t; - motion_map_t mParameterizedMotions; - LLMotion* mDefaultKeyframeMotion; - LLCharacter* mCharacter; - LLPoseBlender mPoseBlender; - - F32 mEaseInDuration; - F32 mEaseOutDuration; - F32 mDuration; - LLJoint::JointPriority mPriority; - - LLUUID mTransactionID; -}; - -#endif // LL_LLKEYFRAMEMOTIONPARAM_H diff --git a/indra/llcharacter/llkeyframestandmotion.cpp b/indra/llcharacter/llkeyframestandmotion.cpp index 1ae0ddeea..bccc714f1 100644 --- a/indra/llcharacter/llkeyframestandmotion.cpp +++ b/indra/llcharacter/llkeyframestandmotion.cpp @@ -50,7 +50,7 @@ const F32 POSITION_THRESHOLD = 0.1f; // LLKeyframeStandMotion() // Class Constructor //----------------------------------------------------------------------------- -LLKeyframeStandMotion::LLKeyframeStandMotion(const LLUUID &id) : LLKeyframeMotion(id) +LLKeyframeStandMotion::LLKeyframeStandMotion(LLUUID const& id, LLMotionController* controller) : LLKeyframeMotion(id, controller) { mFlipFeet = FALSE; mCharacter = NULL; diff --git a/indra/llcharacter/llkeyframestandmotion.h b/indra/llcharacter/llkeyframestandmotion.h index b0500dc8e..346df97f0 100644 --- a/indra/llcharacter/llkeyframestandmotion.h +++ b/indra/llcharacter/llkeyframestandmotion.h @@ -48,7 +48,7 @@ class LLKeyframeStandMotion : { public: // Constructor - LLKeyframeStandMotion(const LLUUID &id); + LLKeyframeStandMotion(LLUUID const& id, LLMotionController* controller); // Destructor virtual ~LLKeyframeStandMotion(); @@ -60,7 +60,7 @@ public: // static constructor // all subclasses must implement such a function and register it - static LLMotion *create(const LLUUID &id) { return new LLKeyframeStandMotion(id); } + static LLMotion* create(LLUUID const& id, LLMotionController* controller) { return new LLKeyframeStandMotion(id, controller); } public: //------------------------------------------------------------------------- diff --git a/indra/llcharacter/llkeyframewalkmotion.cpp b/indra/llcharacter/llkeyframewalkmotion.cpp index f9c2e4766..21d6fce30 100644 --- a/indra/llcharacter/llkeyframewalkmotion.cpp +++ b/indra/llcharacter/llkeyframewalkmotion.cpp @@ -55,8 +55,8 @@ const F32 SPEED_ADJUST_TIME_CONSTANT = 0.1f; // time constant for speed adjustm // LLKeyframeWalkMotion() // Class Constructor //----------------------------------------------------------------------------- -LLKeyframeWalkMotion::LLKeyframeWalkMotion(const LLUUID &id) -: LLKeyframeMotion(id), +LLKeyframeWalkMotion::LLKeyframeWalkMotion(LLUUID const& id, LLMotionController* controller) +: LLKeyframeMotion(id, controller), mCharacter(NULL), mCyclePhase(0.0f), mRealTimeLast(0.0f), @@ -138,8 +138,8 @@ BOOL LLKeyframeWalkMotion::onUpdate(F32 time, U8* joint_mask) // LLWalkAdjustMotion() // Class Constructor //----------------------------------------------------------------------------- -LLWalkAdjustMotion::LLWalkAdjustMotion(const LLUUID &id) : - LLMotion(id), +LLWalkAdjustMotion::LLWalkAdjustMotion(LLUUID const& id, LLMotionController* controller) : + AIMaskedMotion(id, controller, ANIM_AGENT_WALK_ADJUST), mLastTime(0.f), mAnimSpeed(0.f), mAdjustedSpeed(0.f), @@ -193,7 +193,7 @@ BOOL LLWalkAdjustMotion::onActivate() F32 rightAnkleOffset = (mRightAnkleJoint->getWorldPosition() - mCharacter->getCharacterPosition()).magVec(); mAnkleOffset = llmax(leftAnkleOffset, rightAnkleOffset); - return TRUE; + return AIMaskedMotion::onActivate(); } //----------------------------------------------------------------------------- @@ -325,13 +325,14 @@ BOOL LLWalkAdjustMotion::onUpdate(F32 time, U8* joint_mask) void LLWalkAdjustMotion::onDeactivate() { mCharacter->removeAnimationData("Walk Speed"); + AIMaskedMotion::onDeactivate(); } //----------------------------------------------------------------------------- // LLFlyAdjustMotion::LLFlyAdjustMotion() //----------------------------------------------------------------------------- -LLFlyAdjustMotion::LLFlyAdjustMotion(const LLUUID &id) - : LLMotion(id), +LLFlyAdjustMotion::LLFlyAdjustMotion(LLUUID const& id, LLMotionController* controller) + : AIMaskedMotion(id, controller, ANIM_AGENT_FLY_ADJUST), mRoll(0.f) { mName = "fly_adjust"; @@ -368,7 +369,7 @@ BOOL LLFlyAdjustMotion::onActivate() mPelvisState->setPosition(LLVector3::zero); mPelvisState->setRotation(LLQuaternion::DEFAULT); mRoll = 0.f; - return TRUE; + return AIMaskedMotion::onActivate(); } //----------------------------------------------------------------------------- diff --git a/indra/llcharacter/llkeyframewalkmotion.h b/indra/llcharacter/llkeyframewalkmotion.h index b507e9423..653c2b539 100644 --- a/indra/llcharacter/llkeyframewalkmotion.h +++ b/indra/llcharacter/llkeyframewalkmotion.h @@ -52,7 +52,7 @@ class LLKeyframeWalkMotion : friend class LLWalkAdjustMotion; public: // Constructor - LLKeyframeWalkMotion(const LLUUID &id); + LLKeyframeWalkMotion(LLUUID const& id, LLMotionController* controller); // Destructor virtual ~LLKeyframeWalkMotion(); @@ -64,7 +64,7 @@ public: // static constructor // all subclasses must implement such a function and register it - static LLMotion *create(const LLUUID &id) { return new LLKeyframeWalkMotion(id); } + static LLMotion* create(LLUUID const& id, LLMotionController* controller) { return new LLKeyframeWalkMotion(id, controller); } public: //------------------------------------------------------------------------- @@ -86,11 +86,11 @@ public: S32 mDownFoot; }; -class LLWalkAdjustMotion : public LLMotion +class LLWalkAdjustMotion : public AIMaskedMotion { public: // Constructor - LLWalkAdjustMotion(const LLUUID &id); + LLWalkAdjustMotion(LLUUID const& id, LLMotionController* controller); public: //------------------------------------------------------------------------- @@ -99,7 +99,7 @@ public: // static constructor // all subclasses must implement such a function and register it - static LLMotion *create(const LLUUID &id) { return new LLWalkAdjustMotion(id); } + static LLMotion* create(LLUUID const& id, LLMotionController* controller) { return new LLWalkAdjustMotion(id, controller); } public: //------------------------------------------------------------------------- @@ -136,11 +136,11 @@ public: F32 mAnkleOffset; }; -class LLFlyAdjustMotion : public LLMotion +class LLFlyAdjustMotion : public AIMaskedMotion { public: // Constructor - LLFlyAdjustMotion(const LLUUID &id); + LLFlyAdjustMotion(LLUUID const& id, LLMotionController* controller); public: //------------------------------------------------------------------------- @@ -149,7 +149,7 @@ public: // static constructor // all subclasses must implement such a function and register it - static LLMotion *create(const LLUUID &id) { return new LLFlyAdjustMotion(id); } + static LLMotion* create(LLUUID const& id, LLMotionController* controller) { return new LLFlyAdjustMotion(id, controller); } public: //------------------------------------------------------------------------- @@ -157,7 +157,6 @@ public: //------------------------------------------------------------------------- virtual LLMotionInitStatus onInitialize(LLCharacter *character); virtual BOOL onActivate(); - virtual void onDeactivate() {}; virtual BOOL onUpdate(F32 time, U8* joint_mask); virtual LLJoint::JointPriority getPriority(){return LLJoint::HIGHER_PRIORITY;} virtual BOOL getLoop() { return TRUE; } diff --git a/indra/llcharacter/llmotion.cpp b/indra/llcharacter/llmotion.cpp index ce926a38a..efd1ad158 100644 --- a/indra/llcharacter/llmotion.cpp +++ b/indra/llcharacter/llmotion.cpp @@ -37,6 +37,125 @@ #include "llmotion.h" #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) +{ +#ifdef SHOW_ASSERT + mReadyEvents = 0; +#endif +} +// //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- @@ -48,10 +167,11 @@ // LLMotion() // Class Constructor //----------------------------------------------------------------------------- -LLMotion::LLMotion( const LLUUID &id ) : +LLMotion::LLMotion(LLUUID const& id, LLMotionController* controller) : mStopped(TRUE), mActive(FALSE), mID(id), + mController(controller), mActivationTimestamp(0.f), mStopTimestamp(0.f), mSendStopTimestamp(F32_MAX), @@ -147,6 +267,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(); } @@ -159,6 +292,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); @@ -174,4 +315,19 @@ BOOL LLMotion::canDeprecate() return TRUE; } +//----------------------------------------------------------------------------- +// AIMaskedMotion +//----------------------------------------------------------------------------- + +BOOL AIMaskedMotion::onActivate() +{ + mController->activated(mMaskBit); + return TRUE; +} + +void AIMaskedMotion::onDeactivate() +{ + mController->deactivated(mMaskBit); +} + // End diff --git a/indra/llcharacter/llmotion.h b/indra/llcharacter/llmotion.h index d6628fd99..a246b08c7 100644 --- a/indra/llcharacter/llmotion.h +++ b/indra/llcharacter/llmotion.h @@ -38,16 +38,85 @@ //----------------------------------------------------------------------------- #include +#include "aisyncclient.h" #include "llerror.h" #include "llpose.h" #include "lluuid.h" 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; @@ -66,7 +135,7 @@ public: }; // Constructor - LLMotion(const LLUUID &id); + LLMotion(LLUUID const& id, LLMotionController* controller); // Destructor virtual ~LLMotion(); @@ -114,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 @@ -181,6 +265,9 @@ protected: //------------------------------------------------------------------------- std::string mName; // instance name assigned by motion controller LLUUID mID; + // + LLMotionController* mController; + // F32 mActivationTimestamp; // time when motion was activated F32 mStopTimestamp; // time when motion was told to stop @@ -199,9 +286,9 @@ protected: class LLTestMotion : public LLMotion { public: - LLTestMotion(const LLUUID &id) : LLMotion(id){} + LLTestMotion(LLUUID const& id, LLMotionController* controller) : LLMotion(id, controller){} ~LLTestMotion() {} - static LLMotion *create(const LLUUID& id) { return new LLTestMotion(id); } + static LLMotion* create(LLUUID const& id, LLMotionController* controller) { return new LLTestMotion(id, controller); } BOOL getLoop() { return FALSE; } F32 getDuration() { return 0.0f; } F32 getEaseInDuration() { return 0.0f; } @@ -223,9 +310,9 @@ public: class LLNullMotion : public LLMotion { public: - LLNullMotion(const LLUUID &id) : LLMotion(id) {} + LLNullMotion(LLUUID const& id, LLMotionController* controller) : LLMotion(id, controller) {} ~LLNullMotion() {} - static LLMotion *create(const LLUUID &id) { return new LLNullMotion(id); } + static LLMotion* create(LLUUID const& id, LLMotionController* controller) { return new LLNullMotion(id, controller); } // motions must specify whether or not they loop /*virtual*/ BOOL getLoop() { return TRUE; } @@ -266,5 +353,41 @@ public: // called when a motion is deactivated /*virtual*/ void onDeactivate() {} }; + + +//----------------------------------------------------------------------------- +// AIMaskedMotion +//----------------------------------------------------------------------------- + +// These motions have a bit assigned in LLMotionController::mActiveMask +// that is set and uset upon activation/deactivation. + +// This must be in the same order as ANIM_AGENT_BODY_NOISE_ID through ANIM_AGENT_WALK_ADJUST_ID in llvoavatar.cpp. +U32 const ANIM_AGENT_BODY_NOISE = 0x001; +U32 const ANIM_AGENT_BREATHE_ROT = 0x002; +U32 const ANIM_AGENT_PHYSICS_MOTION = 0x004; +U32 const ANIM_AGENT_EDITING = 0x008; +U32 const ANIM_AGENT_EYE = 0x010; +U32 const ANIM_AGENT_FLY_ADJUST = 0x020; +U32 const ANIM_AGENT_HAND_MOTION = 0x040; +U32 const ANIM_AGENT_HEAD_ROT = 0x080; +U32 const ANIM_AGENT_PELVIS_FIX = 0x100; +U32 const ANIM_AGENT_TARGET = 0x200; +U32 const ANIM_AGENT_WALK_ADJUST = 0x400; + +class AIMaskedMotion : public LLMotion +{ +private: + U32 mMaskBit; + +public: + AIMaskedMotion(LLUUID const& id, LLMotionController* controller, U32 mask_bit) : LLMotion(id, controller), mMaskBit(mask_bit) { } + + /*virtual*/ BOOL onActivate(); + /*virtual*/ void onDeactivate(); + + U32 getMaskBit(void) const { return mMaskBit; } +}; + #endif // LL_LLMOTION_H diff --git a/indra/llcharacter/llmotioncontroller.cpp b/indra/llcharacter/llmotioncontroller.cpp index 35dcc1057..d12b3ddb6 100644 --- a/indra/llcharacter/llmotioncontroller.cpp +++ b/indra/llcharacter/llmotioncontroller.cpp @@ -97,7 +97,7 @@ void LLMotionRegistry::markBad( const LLUUID& id ) //----------------------------------------------------------------------------- // createMotion() //----------------------------------------------------------------------------- -LLMotion *LLMotionRegistry::createMotion( const LLUUID &id ) +LLMotion* LLMotionRegistry::createMotion(LLUUID const& id, LLMotionController* controller) { LLMotionConstructor constructor = get_if_there(mMotionTable, id, LLMotionConstructor(NULL)); LLMotion* motion = NULL; @@ -105,11 +105,11 @@ LLMotion *LLMotionRegistry::createMotion( const LLUUID &id ) if ( constructor == NULL ) { // *FIX: need to replace with a better default scheme. RN - motion = LLKeyframeMotion::create(id); + motion = LLKeyframeMotion::create(id, controller); } else { - motion = constructor(id); + motion = constructor(id, controller); } return motion; @@ -126,18 +126,22 @@ LLMotion *LLMotionRegistry::createMotion( const LLUUID &id ) // Class Constructor //----------------------------------------------------------------------------- LLMotionController::LLMotionController() - : mTimeFactor(sCurrentTimeFactor), + : mIsSelf(FALSE), + mTimeFactor(sCurrentTimeFactor), mCharacter(NULL), - mAnimTime(0.f), + mActiveMask(0), + mDisableSyncing(0), + mHidden(false), + mHaveVisibleSyncedMotions(false), mPrevTimerElapsed(0.f), + mAnimTime(0.f), mLastTime(0.0f), mHasRunOnce(FALSE), mPaused(FALSE), mPauseTime(0.f), mTimeStep(0.f), mTimeStepCount(0), - mLastInterp(0.f), - mIsSelf(FALSE) + mLastInterp(0.f) { } @@ -168,7 +172,15 @@ void LLMotionController::deleteAllMotions() mLoadingMotions.clear(); mLoadedMotions.clear(); mActiveMotions.clear(); - + // + 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(); } @@ -178,26 +190,19 @@ void LLMotionController::deleteAllMotions() //----------------------------------------------------------------------------- void LLMotionController::purgeExcessMotions() { - if (mLoadedMotions.size() > MAX_MOTION_INSTANCES) + // + // The old code attempted to remove non-active motions from mDeprecatedMotions, + // but that is nonsense: there are no motions in mDeprecatedMotions that are not active. + if (mLoadedMotions.size() <= MAX_MOTION_INSTANCES) { - // clean up deprecated motions - for (motion_set_t::iterator deprecated_motion_it = mDeprecatedMotions.begin(); - deprecated_motion_it != mDeprecatedMotions.end(); ) - { - motion_set_t::iterator cur_iter = deprecated_motion_it++; - LLMotion* cur_motionp = *cur_iter; - if (!isMotionActive(cur_motionp)) - { - // Motion is deprecated so we know it's not cannonical, - // we can safely remove the instance - removeMotionInstance(cur_motionp); // modifies mDeprecatedMotions - mDeprecatedMotions.erase(cur_iter); - } - } + // Speed up, no need to create motions_to_kill. + return; } + // std::set motions_to_kill; - if (mLoadedMotions.size() > MAX_MOTION_INSTANCES) + + if (1) // Singu: leave indentation alone... { // too many motions active this frame, kill all blenders mPoseBlender.clearBlenders(); @@ -308,24 +313,44 @@ BOOL LLMotionController::registerMotion( const LLUUID& id, LLMotionConstructor c void LLMotionController::removeMotion( const LLUUID& id) { LLMotion* motionp = findMotion(id); - mAllMotions.erase(id); - removeMotionInstance(motionp); + // + // If a motion is erased from mAllMotions, it must be deleted. + if (motionp) + { + mAllMotions.erase(id); + removeMotionInstance(motionp); + delete motionp; + } + // } // removes instance of a motion from all runtime structures, but does // not erase entry by ID, as this could be a duplicate instance -// use removeMotion(id) to remove all references to a given motion by id. +// use removeMotion(id) to remove a reference to a given motion by id +// (that will not remove (active) deprecated motions). void LLMotionController::removeMotionInstance(LLMotion* motionp) { if (motionp) { llassert(findMotion(motionp->getID()) != motionp); - if (motionp->isActive()) - motionp->deactivate(); mLoadingMotions.erase(motionp); mLoadedMotions.erase(motionp); mActiveMotions.remove(motionp); - delete motionp; + // + // Deactivation moved here. Only delete motionp when it is being removed from mDeprecatedMotions. + if (motionp->isActive()) + { + motionp->deactivate(); + // If a motion is deactivated, it must be removed from mDeprecatedMotions if there. + motion_set_t::iterator found_it = mDeprecatedMotions.find(motionp); + if (found_it != mDeprecatedMotions.end()) + { + mDeprecatedMotions.erase(found_it); + // If a motion is erased from mDeprecatedMotions, it must be deleted. + delete motionp; + } + } + // } } @@ -341,7 +366,7 @@ LLMotion* LLMotionController::createMotion( const LLUUID &id ) if (!motion) { // look up constructor and create it - motion = sRegistry.createMotion(id); + motion = sRegistry.createMotion(id, this); if (!motion) { return NULL; @@ -393,6 +418,7 @@ BOOL LLMotionController::startMotion(const LLUUID &id, F32 start_offset) if (motion && !mPaused && motion->canDeprecate() + && motion->isActive() // singu: do not deprecate motions that are not active. && motion->getFadeWeight() > 0.01f // not LOD-ed out && (motion->isBlending() || motion->getStopTime() != 0.f)) { @@ -418,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; } @@ -774,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) @@ -782,12 +832,15 @@ void LLMotionController::updateLoadingMotions() llinfos << "Motion " << motionp->getID() << " init failed." << llendl; sRegistry.markBad(motionp->getID()); mLoadingMotions.erase(curiter); - motion_set_t::iterator found_it = mDeprecatedMotions.find(motionp); - if (found_it != mDeprecatedMotions.end()) - { - mDeprecatedMotions.erase(found_it); - } + // Singu note: a motion in mLoadingMotions will not be in mActiveMotions + // and therefore not be in mDeprecatedMotions. So, we don't have to + // 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; } } @@ -821,8 +874,15 @@ void LLMotionController::updateMotions(bool force_update) { F32 time_interval = fmodf(update_time, mTimeStep); - // always animate *ahead* of actual time - S32 quantum_count = llmax(0, llfloor((update_time - time_interval) / mTimeStep)) + 1; + // + // This old code is nonsense. + //S32 quantum_count = llmax(0, llround((update_time - time_interval) / mTimeStep)) + 1; + // (update_time - time_interval) / mTimeStep is an integer! We need llround to get rid of floating point errors, not llfloor. + // Moreover, just rounding off to the nearest integer with llround(update_time / mTimeStep) makes a lot more sense: + // it is the best we can do to get as close to what we should draw as possible. + // However, mAnimTime may only be incremented; therefore make sure of that with the llmax. + S32 quantum_count = llmax(llround(update_time / mTimeStep), llceil(mAnimTime / mTimeStep)); + // if (quantum_count == mTimeStepCount) { // we're still in same time quantum as before, so just interpolate and exit @@ -848,7 +908,8 @@ void LLMotionController::updateMotions(bool force_update) } else { - mAnimTime = update_time; + // Singu note: mAnimTime may never be set back in time. + mAnimTime = llmax(mAnimTime, update_time); } } @@ -915,6 +976,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 @@ -970,18 +1037,16 @@ BOOL LLMotionController::activateMotionInstance(LLMotion *motion, F32 time) //----------------------------------------------------------------------------- BOOL LLMotionController::deactivateMotionInstance(LLMotion *motion) { - motion->deactivate(); - motion_set_t::iterator found_it = mDeprecatedMotions.find(motion); if (found_it != mDeprecatedMotions.end()) { // deprecated motions need to be completely excised - removeMotionInstance(motion); - mDeprecatedMotions.erase(found_it); + removeMotionInstance(motion); // singu note: this deactivates motion and removes it from mDeprecatedMotions. } else { // for motions that we are keeping, simply remove from active queue + motion->deactivate(); // singu note: moved here from the top of the function. mActiveMotions.remove(motion); } @@ -1049,11 +1114,26 @@ void LLMotionController::dumpMotions() state_string += std::string("L"); if (std::find(mActiveMotions.begin(), mActiveMotions.end(), motion)!=mActiveMotions.end()) state_string += std::string("A"); - if (mDeprecatedMotions.find(motion) != mDeprecatedMotions.end()) - state_string += std::string("D"); + llassert(mDeprecatedMotions.find(motion) == mDeprecatedMotions.end()); // singu: it's impossible that a motion is in mAllMotions and mDeprecatedMotions at the same time. llinfos << gAnimLibrary.animationName(id) << " " << state_string << llendl; - } + // + // Also dump the deprecated motions. + for (motion_set_t::iterator iter = mDeprecatedMotions.begin(); + iter != mDeprecatedMotions.end(); ++iter) + { + std::string state_string; + LLMotion* motion = *iter; + LLUUID id = motion->getID(); + llassert(mLoadingMotions.find(motion) == mLoadingMotions.end()); + if (mLoadedMotions.find(motion) != mLoadedMotions.end()) + state_string += std::string("L"); + if (std::find(mActiveMotions.begin(), mActiveMotions.end(), motion)!=mActiveMotions.end()) + state_string += std::string("A"); + state_string += "D"; + llinfos << gAnimLibrary.animationName(id) << " " << state_string << llendl; + } + // } //----------------------------------------------------------------------------- @@ -1061,11 +1141,11 @@ void LLMotionController::dumpMotions() //----------------------------------------------------------------------------- void LLMotionController::deactivateAllMotions() { - for (motion_map_t::iterator iter = mAllMotions.begin(); - iter != mAllMotions.end(); iter++) + // 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();) { - LLMotion* motionp = iter->second; - deactivateMotionInstance(motionp); + deactivateMotionInstance(*iter++); // This might invalidate iter by erasing it from mActiveMotions. } } @@ -1086,8 +1166,7 @@ void LLMotionController::flushAllMotions() active_motions.push_back(std::make_pair(motionp->getID(),dtime)); motionp->deactivate(); // don't call deactivateMotionInstance() because we are going to reactivate it } - mActiveMotions.clear(); - + // delete all motion instances deleteAllMotions(); @@ -1096,13 +1175,136 @@ 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->isActive()) // 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->isActive()) // 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; + } + } + } +} + +void LLMotionController::pauseAllSyncedCharacters(std::vector& avatar_pause_handles) +{ + // 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->isActive()) // Skip motions that aren't synchronized at all or that are not active. + { + // Run over all clients of the found servers. + 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; + } + controller->requestPause(avatar_pause_handles); + } + } + } +} + +void LLMotionController::requestPause(std::vector& avatar_pause_handles) +{ + if (mCharacter) + { + mCharacter->requestPause(avatar_pause_handles); + } +} + +// + //----------------------------------------------------------------------------- // pause() //----------------------------------------------------------------------------- diff --git a/indra/llcharacter/llmotioncontroller.h b/indra/llcharacter/llmotioncontroller.h index 2de13aa36..054f1c2f9 100644 --- a/indra/llcharacter/llmotioncontroller.h +++ b/indra/llcharacter/llmotioncontroller.h @@ -51,11 +51,14 @@ // This is necessary because llcharacter.h includes this file. //----------------------------------------------------------------------------- class LLCharacter; +class LLMotionController; +class LLPauseRequestHandle; +typedef LLPointer LLAnimPauseRequest; //----------------------------------------------------------------------------- // LLMotionRegistry //----------------------------------------------------------------------------- -typedef LLMotion*(*LLMotionConstructor)(const LLUUID &id); +typedef LLMotion* (*LLMotionConstructor)(LLUUID const& id, LLMotionController*); class LLMotionRegistry { @@ -72,7 +75,7 @@ public: // creates a new instance of a named motion // returns NULL motion is not registered - LLMotion *createMotion( const LLUUID &id ); + LLMotion* createMotion(LLUUID const& id, LLMotionController* controller); // initialization of motion failed, don't try to create this motion again void markBad( const LLUUID& id ); @@ -115,7 +118,6 @@ public: // unregisters a motion with the controller // (actually just forwards call to motion registry) - // returns true if successfull void removeMotion( const LLUUID& id ); // start motion @@ -150,10 +152,20 @@ 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(); void unpauseAllMotions(); BOOL isPaused() const { return mPaused; } + // + void requestPause(std::vector& avatar_pause_handles); + void pauseAllSyncedCharacters(std::vector& avatar_pause_handles); + // void setTimeStep(F32 step); @@ -180,7 +192,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); @@ -205,10 +220,13 @@ protected: // Life cycle of an animation: // // Animations are instantiated and immediately put in the mAllMotions map for their entire lifetime. +// Singu note: that is not true, they are moved to mDeprecatedMotions (often) for the last part of their lifetime. // If the animations depend on any asset data, the appropriate data is fetched from the data server, // and the animation is put on the mLoadingMotions list. // Once an animations is loaded, it will be initialized and put on the mLoadedMotions list. // Any animation that is currently playing also sits in the mActiveMotions list. +// Singu note: animations are only put in mDeprecatedMotions if and while they are playing, +// therefore animations in mDeprecatedMotions will be (must be) active and in mActiveMotions. typedef std::map motion_map_t; motion_map_t mAllMotions; @@ -217,7 +235,13 @@ protected: motion_set_t mLoadedMotions; 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; @@ -230,6 +254,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/llcharacter/lltargetingmotion.cpp b/indra/llcharacter/lltargetingmotion.cpp index a330b2265..05b0fdfe8 100644 --- a/indra/llcharacter/lltargetingmotion.cpp +++ b/indra/llcharacter/lltargetingmotion.cpp @@ -52,7 +52,7 @@ const F32 TORSO_ROT_FRACTION = 0.5f; // LLTargetingMotion() // Class Constructor //----------------------------------------------------------------------------- -LLTargetingMotion::LLTargetingMotion(const LLUUID &id) : LLMotion(id) +LLTargetingMotion::LLTargetingMotion(LLUUID const& id, LLMotionController* controller) : AIMaskedMotion(id, controller, ANIM_AGENT_TARGET) { mCharacter = NULL; mName = "targeting"; @@ -99,14 +99,6 @@ LLMotion::LLMotionInitStatus LLTargetingMotion::onInitialize(LLCharacter *charac return STATUS_SUCCESS; } -//----------------------------------------------------------------------------- -// LLTargetingMotion::onActivate() -//----------------------------------------------------------------------------- -BOOL LLTargetingMotion::onActivate() -{ - return TRUE; -} - //----------------------------------------------------------------------------- // LLTargetingMotion::onUpdate() //----------------------------------------------------------------------------- @@ -166,12 +158,4 @@ BOOL LLTargetingMotion::onUpdate(F32 time, U8* joint_mask) return result; } -//----------------------------------------------------------------------------- -// LLTargetingMotion::onDeactivate() -//----------------------------------------------------------------------------- -void LLTargetingMotion::onDeactivate() -{ -} - - // End diff --git a/indra/llcharacter/lltargetingmotion.h b/indra/llcharacter/lltargetingmotion.h index 1ec9f80d6..f7b08ee69 100644 --- a/indra/llcharacter/lltargetingmotion.h +++ b/indra/llcharacter/lltargetingmotion.h @@ -48,11 +48,11 @@ // class LLTargetingMotion //----------------------------------------------------------------------------- class LLTargetingMotion : - public LLMotion + public AIMaskedMotion { public: // Constructor - LLTargetingMotion(const LLUUID &id); + LLTargetingMotion(LLUUID const& id, LLMotionController* controller); // Destructor virtual ~LLTargetingMotion(); @@ -64,7 +64,7 @@ public: // static constructor // all subclasses must implement such a function and register it - static LLMotion *create(const LLUUID &id) { return new LLTargetingMotion(id); } + static LLMotion* create(LLUUID const& id, LLMotionController* controller) { return new LLTargetingMotion(id, controller); } public: //------------------------------------------------------------------------- @@ -96,19 +96,11 @@ public: // must return true to indicate success and be available for activation virtual LLMotionInitStatus onInitialize(LLCharacter *character); - // called when a motion is activated - // must return TRUE to indicate success, or else - // it will be deactivated - virtual BOOL onActivate(); - // called per time step // must return TRUE while it is active, and // must return FALSE when the motion is completed. virtual BOOL onUpdate(F32 time, U8* joint_mask); - // called when a motion is deactivated - virtual void onDeactivate(); - public: LLCharacter *mCharacter; diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 31e4fa484..fd0e8208c 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -20,6 +20,7 @@ set(llcommon_SOURCE_FILES aialert.cpp aifile.cpp aiframetimer.cpp + aisyncclient.cpp aithreadid.cpp imageids.cpp indra_constants.cpp @@ -112,6 +113,7 @@ set(llcommon_HEADER_FILES aifile.h aiframetimer.h airecursive.h + aisyncclient.h aithreadid.h aithreadsafe.h bitpack.h diff --git a/indra/llcommon/aisyncclient.cpp b/indra/llcommon/aisyncclient.cpp new file mode 100644 index 000000000..e1865fec8 --- /dev/null +++ b/indra/llcommon/aisyncclient.cpp @@ -0,0 +1,698 @@ +/** + * @file aisyncclient.cpp + * + * Copyright (c) 2013, Aleric Inglewood. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution. + * + * CHANGELOG + * and additional copyright holders. + * + * 13/12/2013 + * - Initial version, written by Aleric Inglewood @ SL + */ + +/* + * AISyncClient : the base class of a client object (LLMotion) that needs to be informed + * of the state of other such objects and/or to poll a server object about the state of + * other such objects, in order to be and stay synchronized with those other objects. + * In the case of an LLMotion (animation), all clients would be started and stopped at + * the same time, as long as they are part of the same synchronization group (AISyncServer). + * + * AISyncKey: object that determines what synchronization group a client belongs to. + * When a new client is created, a new AISyncKey is created too, using information from + * the client, the current frame number and the current frame time. If two AISyncKey + * compare equal, using operator==(AISyncKey const& key1, AISyncKey const& key2), + * then the clients that created them need to be synchronized. + * + * AISyncServer: object that represents a group of clients that need to be synchronized: + * it's a wrapper around a std::list with pointers to all the clients + * that need to be synchronized. It also stores the AISyncKey of the first (oldest) client + * that was added. Clients with keys that compare equal to that will be added. + * If a client is added with a synckeytype_t that is larger then it always replaces + * the existing key however. + * + * AISyncServerMap: A wrapper around std::list > + * that stores pointers to all currently existing AISyncServer objects. New entries + * are added at the end, so the oldest key is at the front. + * + * AISyncServerMap list + - - - - - - + - - - - - - + ... The AISyncServerMap is + * | | | a list with refcounted + * V V V pointers to AISyncServers. + * AISyncClient +--> AISyncServer + * | <--- list key ---> AISyncKey Each AISyncServer is a + * DerivedClient . list of normal pointers + * . AISyncClients and one + * <--- . pointer to a AISyncKey. + * Each AISyncClient is the + * base class of a DerivedClient + * and pointers back to the + * server with a refcounted + * pointer. + * + * A new client is passed to the AISyncServerMap to be stored in a new or existing AISyncServer + * object, using the key that the client produces. A boost::intrusive_ptr member + * of the client is set to point to this server. + * + * The lifetime of the server objects is determined by the intrusive_ptr objects that + * point to it: all the clients (which have an externally determined lifetime) and one + * pointer in the AISyncServerMap. However, regularly a check is done on all servers in + * the list to find expired servers: objects with keys older than two frames and older + * than 0.1 seconds; if such a server is found and it has zero or one client, then the + * client is unregistered and the pointer (and thus the server) removed from the + * AISyncServerMap. If it has two or more clients then the entry is kept until both + * clients are removed, which therefore can only be detected in intrusive_ptr_release + * which only has access to the server object. The server then is removed from the list + * by searching through it for the pointer to the server. + */ + +#include "sys.h" +#include "aisyncclient.h" +#include +#include +#include "debug.h" + +bool operator==(AISyncKey const& key1, AISyncKey const& key2) +{ + // Test if these keys match based on time. + if (std::abs((S32)(key1.mStartFrameCount - key2.mStartFrameCount)) > 1 && + std::abs(key1.mFrameTimer.getStartTime() - key2.mFrameTimer.getStartTime()) >= AISyncKey::sExpirationTime) + { + return false; + } + // Time matches, let the derived classes determine if they also match otherwise. + return key1.equals(key2); +} + +#ifdef CWDEBUG +struct SyncEventSet { + synceventset_t mBits; + SyncEventSet(synceventset_t bits) : mBits(bits) { } +}; + +std::ostream& operator<<(std::ostream& os, SyncEventSet const& ses) +{ + for (int b = sizeof(ses.mBits) * 8 - 1; b >= 0; --b) + { + int m = 1 << b; + os << ((ses.mBits & m) ? '1' : '0'); + } + return os; +} + +void print_clients(AISyncServer const* server, AISyncServer::client_list_t const& client_list) +{ + Dout(dc::notice, "Clients of server " << server << ": "); + for (AISyncServer::client_list_t::const_iterator iter = client_list.begin(); iter != client_list.end(); ++ iter) + { + llassert(iter->mClientPtr->mReadyEvents == iter->mReadyEvents); + Dout(dc::notice, "-> " << iter->mClientPtr << " : " << SyncEventSet(iter->mReadyEvents)); + } +} +#endif + +void AISyncServerMap::register_client(AISyncClient* client, AISyncKey* new_key) +{ + // 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); + + // Find if a server with this key already exists. + AISyncServer* server = NULL; + for (server_list_t::iterator iter = mServers.begin(); iter != mServers.end();) + { + boost::intrusive_ptr& server_ptr = *iter++; // Immediately increment iter because the call to unregister_last_client will erase it. + AISyncKey const& server_key(server_ptr->key()); + if (server_key.is_older_than(*new_key)) // This means that the server key is expired: a new key will never match. + { + if (server_ptr->never_synced()) // This means that it contains one or zero clients and will never contain more. + { + // Get rid of this server. + server_ptr->unregister_last_client(); // This will cause the server to be deleted, and erased from mServers. + } + continue; + } + if (*new_key == server_key) + { + server = server_ptr.get(); + // mServers stores new servers in strict order of the creation time of the keys, + // so once we find a server with a key that is equal, none of the remaining servers + // will have expired if they were never synced and we're done with the loop. + // Servers that synced might have been added later, but we don't unregister + // clients from those anyway because their sync partner might still show up. + break; + } + } + + if (server) + { + // A server already exists. + // Keep the oldest key, unless this new key has a synckeytype_t that is larger! + if (new_key->getkeytype() > server->key().getkeytype()) + { + server->swapkey(new_key); + } + delete new_key; + } + else + { + // Create a new server for this client. Transfers the ownership of the key allocation to the server. + 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', + server_list_t::iterator new_where = where; + while (where != mServers.begin()) // unless there exists a server before that + { + --new_where; + if (new_key->getCreationTime() > (*new_where)->key().getCreationTime()) // and the new key is not younger then that, + { + break; + } + where = new_where; // then insert it before that element (etc). + } + // 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); + } + + // Add the client to the server. + server->add(client); +} + +#ifdef SYNC_TESTSUITE +void AISyncServer::sanity_check(void) const +{ + synceventset_t ready_events = (synceventset_t)-1; // All clients are ready. + client_list_t::const_iterator client_iter = mClients.begin(); + while (client_iter != mClients.end()) + { + ready_events &= client_iter->mReadyEvents; + ++client_iter; + } + synceventset_t pending_events = 0; // At least one client is ready. + client_iter = mClients.begin(); + while (client_iter != mClients.end()) + { + pending_events |= client_iter->mReadyEvents; + ++client_iter; + } + llassert(ready_events == mReadyEvents); + llassert(pending_events == mPendingEvents); +} +#endif + +void AISyncServer::add(AISyncClient* client) +{ +#ifdef SYNC_TESTSUITE + sanity_check(); +#endif + + // The client can't already be ready when it isn't even added to a server yet. + llassert(!client->mReadyEvents); + synceventset_t old_ready_events = mReadyEvents; + // A new client is not ready for anything. + mReadyEvents = 0; + // Set mSynchronized if after adding client we'll have more than 1 client (that prevents the + // server from being deleted unless all clients are actually destructed or explicitly unregistered). + if (!mSynchronized && mClients.size() > 0) + { + mSynchronized = true; + } + // Trigger the existing clients to be not-ready anymore (if we were before). + trigger(old_ready_events); + // Only then add the new client (so that it didn't get not-ready trigger). + mClients.push_back(client); + client->mServer = this; + +#ifdef SYNC_TESTSUITE + sanity_check(); +#endif +} + +void AISyncServer::remove(AISyncClient* client) +{ +#ifdef SYNC_TESTSUITE + sanity_check(); +#endif + + 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). + client_list_t::iterator found_client = mClients.end(); + while (client_iter != mClients.end()) + { + if (client_iter->mClientPtr == client) + { + found_client = client_iter; + } + else + { + remaining_ready_events &= client_iter->mReadyEvents; + remaining_pending_events |= client_iter->mReadyEvents; + } + ++client_iter; + } + llassert(found_client != mClients.end()); + // This must be the same as client->mReadyEvents. + llassert(found_client->mReadyEvents == client->mReadyEvents); + mClients.erase(found_client); + synceventset_t old_ready_events = mReadyEvents; + mReadyEvents = remaining_ready_events; + mPendingEvents = remaining_pending_events; + trigger(old_ready_events); + client->mServer.reset(); + client->deregistered(); + +#ifdef SYNC_TESTSUITE + sanity_check(); +#endif +} + +void AISyncServer::unregister_last_client(void) +{ +#ifdef SYNC_TESTSUITE + sanity_check(); +#endif + + // This function may only be called for servers with exactly one client that was never (potentially) synchronized. + llassert(!mSynchronized && mClients.size() == 1); + AISyncClient* client = mClients.begin()->mClientPtr; + mClients.clear(); + client->mServer.reset(); + llassert(mReadyEvents == client->mReadyEvents); + llassert(mPendingEvents == mReadyEvents); + // No need to update mReadyEvents/mPendingEvents because the server is going to be deleted, + // but inform the client that is no longer has a server. + client->deregistered(); + +#ifdef SYNC_TESTSUITE + sanity_check(); +#endif +} + +void AISyncServer::trigger(synceventset_t old_ready_events) +{ + // If event 1 changed, informat all clients about it. + if (((old_ready_events ^ mReadyEvents) & 1)) + { + for (client_list_t::iterator client_iter = mClients.begin(); client_iter != mClients.end(); ++client_iter) + { + if ((mReadyEvents & 1)) + { + client_iter->mClientPtr->event1_ready(); + } + else + { + client_iter->mClientPtr->event1_not_ready(); + } + } + } +} + +void AISyncServer::ready(synceventset_t events, synceventset_t yesno, AISyncClient* client) +{ +#ifdef SYNC_TESTSUITE + sanity_check(); +#endif + + synceventset_t added_events = events & yesno; + synceventset_t removed_events = events & ~yesno; + + // 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. + synceventset_t remaining_pending_events = 0; // At least one client is ready (waiting for the other clients thus). + client_list_t::iterator found_client = mClients.end(); + for (client_list_t::iterator client_iter = mClients.begin(); client_iter != mClients.end(); ++client_iter) + { + if (client_iter->mClientPtr == client) + { + found_client = client_iter; + } + else + { + remaining_ready_events &= client_iter->mReadyEvents; + remaining_pending_events |= client_iter->mReadyEvents; + } + } + + llassert(mReadyEvents == (remaining_ready_events & found_client->mReadyEvents)); + llassert(mPendingEvents == (remaining_pending_events | found_client->mReadyEvents)); + + found_client->mReadyEvents &= ~removed_events; + found_client->mReadyEvents |= added_events; +#ifdef SHOW_ASSERT + client->mReadyEvents = found_client->mReadyEvents; +#endif + + synceventset_t old_ready_events = mReadyEvents; + + mReadyEvents = remaining_ready_events & found_client->mReadyEvents; + mPendingEvents = remaining_pending_events | found_client->mReadyEvents; + + trigger(old_ready_events); + +#ifdef SYNC_TESTSUITE + sanity_check(); +#endif +} + +void intrusive_ptr_add_ref(AISyncServer* server) +{ + server->mRefCount++; +} + +void intrusive_ptr_release(AISyncServer* server) +{ + llassert(server->mRefCount > 0); + server->mRefCount--; + if (server->mRefCount == 0) + { + delete server; + } + else if (server->mRefCount == 1) + { + // If the the last pointer to this server is in the the AISyncServerMap, then delete that too. + AISyncServerMap::instance().remove_server(server); + } +} + +void AISyncServerMap::remove_server(AISyncServer* server) +{ + for (server_list_t::iterator iter = mServers.begin(); iter != mServers.end(); ++iter) + { + if (server == iter->get()) + { + mServers.erase(iter); // This causes server to be deleted too. + return; + } + } + // The server must be found: this function is only called from intrusive_ptr_release for servers + // with just a single server_ptr_t left, which must be the one in mServers (otherwise it wasn't + // even registered properly!) + llassert(false); +} + + +//============================================================================= +// SYNC_TESTSUITE +//============================================================================= + +#ifdef SYNC_TESTSUITE +#include +#include +#include +#include + +//static +U32 LLFrameTimer::sFrameCount; + +double innerloop_count = 0; + +double LLFrameTimer::getCurrentTime() +{ + return innerloop_count * 0.001; +} + +template +class TestsuiteKey : public AISyncKey +{ + private: + int mIndex; + + public: + TestsuiteKey(int index) : mIndex(index) { } + + int getIndex(void) const { return mIndex; } + + // 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; + } + + /*virtual*/ bool equals(AISyncKey const& key) const + { + synckeytype_t const theotherkey = (synckeytype_t)(synckeytype ^ 0x100); + switch (key.getkeytype()) + { + case synckeytype: + { + // The other key is of the same type. + TestsuiteKey const& test_key = static_cast const&>(key); + return (mIndex & 1) == (test_key.mIndex & 1); + } + case theotherkey: + { + TestsuiteKey const& test_key = static_cast const&>(key); + return (mIndex & 2) == (test_key.getIndex() & 2); + } + default: + // The keys must be in the same syncgroup. + break; + } + return false; + } +}; + +template +class TestsuiteClient : public AISyncClient +{ + // AISyncClient events. + protected: + /*virtual*/ AISyncKey* createSyncKey(void) const + { + return new TestsuiteKey(mIndex); + } + + private: + int mIndex; + bool mRequestedRegistered; + synceventset_t mRequestedReady; + bool mActualReady1; + + public: + TestsuiteClient() : mIndex(-1), mRequestedRegistered(false), mRequestedReady(0), mActualReady1(false) { } + ~TestsuiteClient() { if (is_registered()) this->ready(mRequestedReady, (synceventset_t)0); } + + void setIndex(int index) { mIndex = index; } + + protected: + /*virtual*/ void event1_ready(void) + { +#ifdef DEBUG_SYNCOUTPUT + Dout(dc::notice, "Calling TestsuiteClient<" << synckeytype << ">::event1_ready() (mIndex = " << mIndex << ") of client " << this); +#endif + llassert(!mActualReady1); + mActualReady1 = true; + } + + /*virtual*/ void event1_not_ready(void) + { +#ifdef DEBUG_SYNCOUTPUT + Dout(dc::notice, "Calling TestsuiteClient<" << synckeytype << ">::event1_not_ready() (mIndex = " << mIndex << ") of client " << this); +#endif + llassert(mActualReady1); + mActualReady1 = false; + } + + // This is called when the server expired and we're the only client on it. + /*virtual*/ void deregistered(void) + { +#ifdef DEBUG_SYNCOUTPUT + DoutEntering(dc::notice, "TestsuiteClient<" << synckeytype << ">::deregistered(), with this = " << this); +#endif + mRequestedRegistered = false; + mRequestedReady = 0; + mActualReady1 = false; + this->mReadyEvents = 0; + } + + private: + bool is_registered(void) const { return this->server(); } + + public: + void change_state(unsigned long r); + bool getRequestedRegistered(void) const { return mRequestedRegistered; } + synceventset_t getRequestedReady(void) const { return mRequestedReady; } +}; + +TestsuiteClient* client1ap; +TestsuiteClient* client1bp; +TestsuiteClient* client2ap; +TestsuiteClient* client2bp; + +int const number_of_clients_per_syncgroup = 8; + +template +void TestsuiteClient::change_state(unsigned long r) +{ + bool change_registered = r & 1; + r >>= 1; + synceventset_t toggle_events = r & 15; + r >>= 4; + if (change_registered) + { + if (mRequestedRegistered && !mRequestedReady) + { + mRequestedRegistered = false; + this->unregister_client(); + } + } + else if (toggle_events) + { + mRequestedReady ^= toggle_events; + mRequestedRegistered = true; + this->ready(toggle_events, mRequestedReady & toggle_events); + } + llassert(mRequestedRegistered == is_registered()); + AISyncServer* server = this->server(); + if (mRequestedRegistered) + { + synceventset_t all_ready = synceventset_t(-1); + synceventset_t any_ready = 0; + int nr = 0; + for (int cl = 0; cl < number_of_clients_per_syncgroup; ++cl) + { + switch ((synckeytype & 0xff)) + { + case syncgroup_test1: + { + if (client1ap[cl].server() == server) + { + if (client1ap[cl].getRequestedRegistered()) + { + ++nr; + all_ready &= client1ap[cl].getRequestedReady(); + any_ready |= client1ap[cl].getRequestedReady(); + } + } + if (client1bp[cl].server() == server) + { + if (client1bp[cl].getRequestedRegistered()) + { + ++nr; + all_ready &= client1bp[cl].getRequestedReady(); + any_ready |= client1bp[cl].getRequestedReady(); + } + } + break; + } + case syncgroup_test2: + { + if (client2ap[cl].server() == server) + { + if (client2ap[cl].getRequestedRegistered()) + { + ++nr; + all_ready &= client2ap[cl].getRequestedReady(); + any_ready |= client2ap[cl].getRequestedReady(); + } + } + if (client2bp[cl].server() == server) + { + if (client2bp[cl].getRequestedRegistered()) + { + ++nr; + all_ready &= client2bp[cl].getRequestedReady(); + any_ready |= client2bp[cl].getRequestedReady(); + } + } + break; + } + } + } + llassert(nr == server->getClients().size()); + llassert(!!(all_ready & 1) == mActualReady1); + llassert(this->server()->events_with_all_clients_ready() == all_ready); + llassert(this->server()->events_with_at_least_one_client_ready() == any_ready); + llassert(nr == 0 || (any_ready & all_ready) == all_ready); + } + llassert(mRequestedReady == this->mReadyEvents); +} + +int main() +{ + Debug(libcw_do.on()); + Debug(dc::notice.on()); + Debug(libcw_do.set_ostream(&std::cout)); + Debug(list_channels_on(libcw_do)); + + unsigned short seed16v[3] = { 0x1234, 0xfedc, 0x7091 }; + + for (int k = 0;; ++k) + { + std::cout << "Loop: " << k << "; SEED: " << std::hex << seed16v[0] << ", " << seed16v[1] << ", " << seed16v[2] << std::dec << std::endl; + ++LLFrameTimer::sFrameCount; + + seed48(seed16v); + seed16v[0] = lrand48() & 0xffff; + seed16v[1] = lrand48() & 0xffff; + seed16v[2] = lrand48() & 0xffff; + + TestsuiteClient client1a[number_of_clients_per_syncgroup]; + TestsuiteClient client1b[number_of_clients_per_syncgroup]; + TestsuiteClient client2a[number_of_clients_per_syncgroup]; + TestsuiteClient client2b[number_of_clients_per_syncgroup]; + client1ap = client1a; + client1bp = client1b; + client2ap = client2a; + client2bp = client2b; + + for (int i = 0; i < number_of_clients_per_syncgroup; ++i) + { + client1a[i].setIndex(i); + client1b[i].setIndex(i); + client2a[i].setIndex(i); + client2b[i].setIndex(i); + } + + for (int j = 0; j < 1000000; ++j) + { + innerloop_count += 1; + +#ifdef DEBUG_SYNCOUTPUT + Dout(dc::notice, "Innerloop: " << j); +#endif + unsigned long r = lrand48(); + synckeytype_t keytype = (r & 1) ? ((r & 2) ? synckeytype_test1a : synckeytype_test1b) : ((r & 2) ? synckeytype_test2a : synckeytype_test2b); + r >>= 2; + int cl = (r & 255) % number_of_clients_per_syncgroup; + r >>= 8; + switch (keytype) + { + case synckeytype_test1a: + client1a[cl].change_state(r); + break; + case synckeytype_test1b: + client1b[cl].change_state(r); + break; + case synckeytype_test2a: + client2a[cl].change_state(r); + break; + case synckeytype_test2b: + client2b[cl].change_state(r); + break; + } + } + } +} + +#endif diff --git a/indra/llcommon/aisyncclient.h b/indra/llcommon/aisyncclient.h new file mode 100644 index 000000000..b810f143a --- /dev/null +++ b/indra/llcommon/aisyncclient.h @@ -0,0 +1,303 @@ +/** + * @file aisyncclient.h + * @brief Declaration of AISyncClient. + * + * Copyright (c) 2013, Aleric Inglewood. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution. + * + * CHANGELOG + * and additional copyright holders. + * + * 12/12/2013 + * Initial version, written by Aleric Inglewood @ SL + */ + +#ifndef AI_SYNC_CLIENT_H +#define AI_SYNC_CLIENT_H + +#ifdef SYNC_TESTSUITE +/* + * To compile the testsuite, run: + * + * cd indra/llcommon + * g++ -O3 -DCWDEBUG -DSYNC_TESTSUITE -I. -I../cwdebug aisyncclient.cpp -lcwd + */ + +#include +#include +typedef uint32_t U32; +typedef int32_t S32; +typedef uint64_t U64; +typedef float F32; +typedef double F64; +#define LL_COMMON_API +#define SHOW_ASSERT +#define ASSERT_ONLY_COMMA(...) , __VA_ARGS__ +#define llassert assert + +struct LLFrameTimer +{ + double mStartTime; + double mExpiry; + static double getCurrentTime(void); + static U32 sFrameCount; + static U32 getFrameCount() { return sFrameCount; } + F64 getStartTime() const { return mStartTime; } + void reset(double expiration) { mStartTime = getCurrentTime(); mExpiry = mStartTime + expiration; } + bool hasExpired(void) const { return getCurrentTime() > mExpiry; } +}; + +template +struct LLSingleton +{ + static T sInstance; + static T& instance(void) { return sInstance; } +}; + +template +T LLSingleton::sInstance; + +#else // !SYNC_TESTSUITE +#include "llsingleton.h" +#include "llframetimer.h" +#endif +#include +#include + +//--------------------------------------------------------------------------------------------------------------------- +// Keys with a different syncgroup are never equal (so they are never synchronized). +enum syncgroups +{ +#ifdef SYNC_TESTSUITE + syncgroup_test1, + syncgroup_test2, +#else + syncgroup_motions, // Syncgroup used for animations. +#endif + syncgroup_size +}; + +// Each key type must return a unique identifier that exists of its syncgroup (the least significant 8 bit) plus a few bit to make it unique (bit 9 and higher). +enum synckeytype_t +{ +#ifdef SYNC_TESTSUITE + synckeytype_test1a = 0x000 + syncgroup_test1, + synckeytype_test1b = 0x100 + syncgroup_test1, + synckeytype_test2a = 0x000 + syncgroup_test2, + synckeytype_test2b = 0x100 + syncgroup_test2, +#else + synckeytype_motion = syncgroup_motions // There is currently only one key type in the syncgroup_motions group: AISyncKeyMotion. +#endif +}; + +typedef U32 synceventset_t; // A mask where each bit represents a ready state. + +class LL_COMMON_API AISyncKey +{ + private: + LLFrameTimer mFrameTimer; // This timer is started at the moment the sync key is created. + U32 mStartFrameCount; // The frame count at which the timer was started. + static F32 const sExpirationTime = 0.1; // In seconds. + + public: + // Constructor. + AISyncKey(AISyncKey const* from_key) : mStartFrameCount(from_key ? from_key->mStartFrameCount : LLFrameTimer::getFrameCount()) + { + if (from_key) + { + mFrameTimer.copy(from_key->mFrameTimer); + } + else + { + mFrameTimer.reset(sExpirationTime); + } + } + + // Destructor. + virtual ~AISyncKey() { } + + // Return true if this key expired. + bool expired(void) const + { + // The key has expired when sExpirationTime seconds have elapsed AND at least two frames have passed. + return mFrameTimer.getFrameCount() > mStartFrameCount + 1 && mFrameTimer.hasExpired(); + } + + // Returns true if this object and key would not compare equal based on time because this object is too old. + bool is_older_than(AISyncKey const& key) const + { + return key.mStartFrameCount > mStartFrameCount + 1 && key.mFrameTimer.getStartTime() > mFrameTimer.getStartTime() + sExpirationTime; + } + + // Return the creation time of this key (in number of seconds since application start). + F64 getCreationTime(void) const { return mFrameTimer.getStartTime(); } + + // Returns true if the two keys match, meaning that they should be synchronized. + friend bool operator==(AISyncKey const& key1, AISyncKey const& key2); + + // Returns an ID that uniquely identifies the derived type. + // Currently the only derived type is AISyncKeyMotion with ID synckeytype_motion. + virtual synckeytype_t getkeytype(void) const = 0; + + // Returns true if the data in the derived objects match, meaning that they should be synchronized. + virtual bool equals(AISyncKey const& key) const = 0; +}; + +// Forward declaration. +class AISyncClient; +class AISyncServer; +LL_COMMON_API extern void intrusive_ptr_add_ref(AISyncServer* server); +LL_COMMON_API extern void intrusive_ptr_release(AISyncServer* server); + +struct LL_COMMON_API AISyncClientData +{ + AISyncClient* mClientPtr; + synceventset_t mReadyEvents; + + AISyncClientData(AISyncClient* client) : mClientPtr(client), mReadyEvents(0) { } +}; + +class LL_COMMON_API AISyncServer +{ + public: + typedef std::list client_list_t; + + private: + int mRefCount; // Number of boost::intrusive_ptr objects pointing to this object. + AISyncKey* mKey; // The key of the first client that was added. + client_list_t mClients; // A list with pointers to all registered clients. + bool mSynchronized; // Set when a server gets more than one client, and not reset anymore after that. + synceventset_t mReadyEvents; // 0xFFFFFFFF bitwise-AND-ed with all clients. + synceventset_t mPendingEvents; // The bitwise-OR of all clients. + + public: + AISyncServer(AISyncKey* key) : mRefCount(0), mKey(key), mSynchronized(false), mReadyEvents((synceventset_t)-1), mPendingEvents(0) { } + ~AISyncServer() { delete mKey; } + + // Add a new client to this server. + void add(AISyncClient* client); + + // 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). + AISyncKey const& key(void) const { return *mKey; } + // Replace they key with another key (of larger synckeytype_t). + void swapkey(AISyncKey*& key_ptr) { AISyncKey* tmp = key_ptr; key_ptr = mKey; mKey = tmp; } + + // Returns true if this server never had more than one client. + bool never_synced(void) const { return !mSynchronized; } + + // Set readiness of all events at once. + 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 { return mReadyEvents; } + + // Return events that at least one client is ready for. + synceventset_t events_with_at_least_one_client_ready(void) const { return mPendingEvents; } + + // Return a list of all registered clients. + client_list_t const& getClients(void) const { return mClients; } + + private: + // Call event1_ready() or event1_not_ready() on all clients if the least significant bit of mReadyEvents changed. + void trigger(synceventset_t old_ready_events); + +#ifdef SYNC_TESTSUITE + void sanity_check(void) const; +#endif + + private: + friend void intrusive_ptr_add_ref(AISyncServer* server); + friend void intrusive_ptr_release(AISyncServer* server); +}; + +class LL_COMMON_API AISyncServerMap : public LLSingleton +{ + public: + typedef boost::intrusive_ptr server_ptr_t; // The type of a (stored) pointer to the server objects. + typedef std::list server_list_t; // The type of the list with pointers to the server objects. + + private: + server_list_t mServers; // A list with pointers to all server objects. + + 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, AISyncKey* new_key); + + private: + friend void intrusive_ptr_release(AISyncServer* server); + // Remove a server from the map, only called by intrusive_ptr_release when there is one pointer left; + // therefore, the server should not have any clients. + void remove_server(AISyncServer* server); +}; + +class LL_COMMON_API AISyncClient +{ + private: + friend class AISyncServer; + boost::intrusive_ptr mServer; // The server this client was registered with, or NULL when unregistered. + + public: +#ifdef SHOW_ASSERT + synceventset_t mReadyEvents; + AISyncClient(void) : mReadyEvents(0) { } +#endif + 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; + + // Only client. Client was forcefully deregistered from expired server because it was the only client. + virtual void deregistered(void) + { +#ifdef SHOW_ASSERT + mReadyEvents = 0; +#endif + } + + 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, createSyncKey()); } + + // 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. + + // Set readiness of all events at once. + void ready(synceventset_t events, synceventset_t yesno) + { + if (!mServer) + { + register_client(); + } + mServer->ready(events, yesno, this); + } +}; + +#endif 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/llemote.cpp b/indra/newview/llemote.cpp index c83846215..47bd57969 100644 --- a/indra/newview/llemote.cpp +++ b/indra/newview/llemote.cpp @@ -48,7 +48,7 @@ // LLEmote() // Class Constructor //----------------------------------------------------------------------------- -LLEmote::LLEmote(const LLUUID &id) : LLMotion(id) +LLEmote::LLEmote(LLUUID const& id, LLMotionController* controller) : LLMotion(id, controller) { mCharacter = NULL; diff --git a/indra/newview/llemote.h b/indra/newview/llemote.h index 99d05b9a2..173719121 100644 --- a/indra/newview/llemote.h +++ b/indra/newview/llemote.h @@ -55,7 +55,7 @@ class LLEmote : { public: // Constructor - LLEmote(const LLUUID &id); + LLEmote(LLUUID const& id, LLMotionController* controller); // Destructor virtual ~LLEmote(); @@ -67,7 +67,7 @@ public: // static constructor // all subclasses must implement such a function and register it - static LLMotion *create(const LLUUID &id) { return new LLEmote(id); } + static LLMotion* create(LLUUID const& id, LLMotionController* controller) { return new LLEmote(id, controller); } public: //------------------------------------------------------------------------- diff --git a/indra/newview/llfloaterbvhpreview.cpp b/indra/newview/llfloaterbvhpreview.cpp index 58e27b914..251397fa6 100644 --- a/indra/newview/llfloaterbvhpreview.cpp +++ b/indra/newview/llfloaterbvhpreview.cpp @@ -375,6 +375,10 @@ BOOL LLFloaterBvhPreview::postBuild() } } + if (motionp && mInWorld) + { + gAgentAvatarp->removeMotion(mMotionID); + } //setEnabled(FALSE); mMotionID.setNull(); mAnimPreview = NULL; diff --git a/indra/newview/llhudeffectlookat.cpp b/indra/newview/llhudeffectlookat.cpp index eef650101..9f6acb60b 100644 --- a/indra/newview/llhudeffectlookat.cpp +++ b/indra/newview/llhudeffectlookat.cpp @@ -611,8 +611,9 @@ void LLHUDEffectLookAt::update() { if (calcTargetPosition()) { - LLMotion* head_motion = ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->findMotion(ANIM_AGENT_HEAD_ROT); - if (!head_motion || head_motion->isStopped()) + //LLMotion* head_motion = ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->findMotion(ANIM_AGENT_HEAD_ROT); + //if (!head_motion || head_motion->isStopped()) + // singu: startMotion does basically the same as the above two lines... it starts it, unless it was already started. { ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->startMotion(ANIM_AGENT_HEAD_ROT); } diff --git a/indra/newview/llhudmanager.cpp b/indra/newview/llhudmanager.cpp index cb9a2c1d3..15872a0db 100644 --- a/indra/newview/llhudmanager.cpp +++ b/indra/newview/llhudmanager.cpp @@ -80,14 +80,33 @@ void LLHUDManager::updateEffects() void LLHUDManager::sendEffects() { + static LLCachedControl disable_lookat_effect(gSavedSettings, "PrivateLookAt", false); + static LLCachedControl disable_pointat_effect(gSavedSettings, "DisablePointAtAndBeam", false); + static LLCachedControl broadcast_viewer_effects(gSavedSettings, "BroadcastViewerEffects", true); - if(!gSavedSettings.getBOOL("BroadcastViewerEffects")) - return; - S32 i; for (i = 0; i < mHUDEffects.count(); i++) { LLHUDEffect *hep = mHUDEffects[i]; + if (hep->mType == LLHUDObject::LL_HUD_EFFECT_LOOKAT) + { + if (disable_lookat_effect) + { + continue; + } + } + else if (hep->mType == LLHUDObject::LL_HUD_EFFECT_POINTAT || + hep->mType == LLHUDObject::LL_HUD_EFFECT_BEAM) + { + if (disable_pointat_effect) + { + continue; + } + } + else if (!broadcast_viewer_effects) + { + continue; + } if (hep->isDead()) { llwarns << "Trying to send dead effect!" << llendl; diff --git a/indra/newview/llphysicsmotion.cpp b/indra/newview/llphysicsmotion.cpp index 6fc0ab438..99ff385ee 100644 --- a/indra/newview/llphysicsmotion.cpp +++ b/indra/newview/llphysicsmotion.cpp @@ -314,8 +314,8 @@ void LLPhysicsMotion::getString(std::ostringstream &oss) } } -LLPhysicsMotionController::LLPhysicsMotionController(const LLUUID &id) : - LLMotion(id), +LLPhysicsMotionController::LLPhysicsMotionController(LLUUID const& id, LLMotionController* controller) : + AIMaskedMotion(id, controller, ANIM_AGENT_PHYSICS_MOTION), mCharacter(NULL), mIsDefault(true) { @@ -332,15 +332,6 @@ LLPhysicsMotionController::~LLPhysicsMotionController() } } -BOOL LLPhysicsMotionController::onActivate() -{ - return TRUE; -} - -void LLPhysicsMotionController::onDeactivate() -{ -} - LLMotion::LLMotionInitStatus LLPhysicsMotionController::onInitialize(LLCharacter *character) { mCharacter = character; @@ -889,4 +880,4 @@ void LLPhysicsMotion::reset() mCharacter->setVisualParamWeight((*iter).mParam,(*iter).mParam->getDefaultWeight()); } } -} \ No newline at end of file +} diff --git a/indra/newview/llphysicsmotion.h b/indra/newview/llphysicsmotion.h index 7412c9d88..2ce79b220 100644 --- a/indra/newview/llphysicsmotion.h +++ b/indra/newview/llphysicsmotion.h @@ -42,14 +42,14 @@ class LLPhysicsMotion; // class LLPhysicsMotion //----------------------------------------------------------------------------- class LLPhysicsMotionController : - public LLMotion + public AIMaskedMotion { public: std::string getString(); // Constructor - LLPhysicsMotionController(const LLUUID &id); + LLPhysicsMotionController(LLUUID const& id, LLMotionController* controller); // Destructor virtual ~LLPhysicsMotionController(); @@ -61,7 +61,7 @@ public: // static constructor // all subclasses must implement such a function and register it - static LLMotion *create(const LLUUID &id) { return new LLPhysicsMotionController(id); } + static LLMotion* create(LLUUID const& id, LLMotionController* controller) { return new LLPhysicsMotionController(id, controller); } public: //------------------------------------------------------------------------- @@ -93,19 +93,11 @@ public: // must return true to indicate success and be available for activation virtual LLMotionInitStatus onInitialize(LLCharacter *character); - // called when a motion is activated - // must return TRUE to indicate success, or else - // it will be deactivated - virtual BOOL onActivate(); - // called per time step // must return TRUE while it is active, and // must return FALSE when the motion is completed. virtual BOOL onUpdate(F32 time, U8* joint_mask); - // called when a motion is deactivated - virtual void onDeactivate(); - LLCharacter* getCharacter() { return mCharacter; } protected: diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp index 4881139df..976397931 100644 --- a/indra/newview/llselectmgr.cpp +++ b/indra/newview/llselectmgr.cpp @@ -6506,7 +6506,7 @@ void LLSelectMgr::updateSelectionCenter() mSelectionCenterGlobal.clearVec(); mShowSelection = FALSE; mSelectionBBox = LLBBox(); - mPauseRequest = NULL; + mPauseRequests.clear(); resetAgentHUDZoom(); } @@ -6516,27 +6516,18 @@ void LLSelectMgr::updateSelectionCenter() if (mSelectedObjects->mSelectType == SELECT_TYPE_ATTACHMENT && isAgentAvatarValid()) { - // Singu Note: Chalice Yao's pause agent on attachment selection - if (object->permYouOwner()) + // Freeze avatars with a selected attachment, and all avatars with synchronized motions, if any. + LLVOAvatar* avatar = object->getAvatar(); + // It is possible that 'avatar' is NULL despite this being an attachment because of some race condition. + // In that case just don't freeze the avatar. + if (avatar) { - mPauseRequest = gAgentAvatarp->requestPause(); - } - else if (LLViewerObject* objectp = mSelectedObjects->getPrimaryObject()) - { - while (objectp && !objectp->isAvatar()) - { - objectp = (LLViewerObject*)objectp->getParent(); - } - - if (objectp) - { - mPauseRequest = objectp->asAvatar()->requestPause(); - } + avatar->pauseAllSyncedCharacters(mPauseRequests); } } else { - mPauseRequest = NULL; + mPauseRequests.clear(); } if (mSelectedObjects->mSelectType != SELECT_TYPE_HUD && isAgentAvatarValid()) diff --git a/indra/newview/llselectmgr.h b/indra/newview/llselectmgr.h index 16c960ba8..145c25005 100644 --- a/indra/newview/llselectmgr.h +++ b/indra/newview/llselectmgr.h @@ -793,7 +793,7 @@ private: LLFrameTimer mEffectsTimer; BOOL mForceSelection; - LLAnimPauseRequest mPauseRequest; + std::vector mPauseRequests; // Selected avatar and all synchronized avatars. friend class LLObjectBackup; }; 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 3b15311e7..901340a57 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -145,18 +145,106 @@ using namespace LLAvatarAppearanceDefines; //----------------------------------------------------------------------------- // Global constants //----------------------------------------------------------------------------- -const LLUUID ANIM_AGENT_BODY_NOISE = LLUUID("9aa8b0a6-0c6f-9518-c7c3-4f41f2c001ad"); //"body_noise" -const LLUUID ANIM_AGENT_BREATHE_ROT = LLUUID("4c5a103e-b830-2f1c-16bc-224aa0ad5bc8"); //"breathe_rot" -const LLUUID ANIM_AGENT_EDITING = LLUUID("2a8eba1d-a7f8-5596-d44a-b4977bf8c8bb"); //"editing" -const LLUUID ANIM_AGENT_EYE = LLUUID("5c780ea8-1cd1-c463-a128-48c023f6fbea"); //"eye" -const LLUUID ANIM_AGENT_FLY_ADJUST = LLUUID("db95561f-f1b0-9f9a-7224-b12f71af126e"); //"fly_adjust" -const LLUUID ANIM_AGENT_HAND_MOTION = LLUUID("ce986325-0ba7-6e6e-cc24-b17c4b795578"); //"hand_motion" -const LLUUID ANIM_AGENT_HEAD_ROT = LLUUID("e6e8d1dd-e643-fff7-b238-c6b4b056a68d"); //"head_rot" -const LLUUID ANIM_AGENT_PELVIS_FIX = LLUUID("0c5dd2a2-514d-8893-d44d-05beffad208b"); //"pelvis_fix" -const LLUUID ANIM_AGENT_TARGET = LLUUID("0e4896cb-fba4-926c-f355-8720189d5b55"); //"target" -const LLUUID ANIM_AGENT_WALK_ADJUST = LLUUID("829bc85b-02fc-ec41-be2e-74cc6dd7215d"); //"walk_adjust" -const LLUUID ANIM_AGENT_PHYSICS_MOTION = LLUUID("7360e029-3cb8-ebc4-863e-212df440d987"); //"physics_motion" +const LLUUID ANIM_AGENT_BODY_NOISE_ID = LLUUID("9aa8b0a6-0c6f-9518-c7c3-4f41f2c001ad"); //"body_noise" +const LLUUID ANIM_AGENT_BREATHE_ROT_ID = LLUUID("4c5a103e-b830-2f1c-16bc-224aa0ad5bc8"); //"breathe_rot" +const LLUUID ANIM_AGENT_PHYSICS_MOTION_ID = LLUUID("7360e029-3cb8-ebc4-863e-212df440d987"); //"physics_motion" +const LLUUID ANIM_AGENT_EDITING_ID = LLUUID("2a8eba1d-a7f8-5596-d44a-b4977bf8c8bb"); //"editing" +const LLUUID ANIM_AGENT_EYE_ID = LLUUID("5c780ea8-1cd1-c463-a128-48c023f6fbea"); //"eye" +const LLUUID ANIM_AGENT_FLY_ADJUST_ID = LLUUID("db95561f-f1b0-9f9a-7224-b12f71af126e"); //"fly_adjust" +const LLUUID ANIM_AGENT_HAND_MOTION_ID = LLUUID("ce986325-0ba7-6e6e-cc24-b17c4b795578"); //"hand_motion" +const LLUUID ANIM_AGENT_HEAD_ROT_ID = LLUUID("e6e8d1dd-e643-fff7-b238-c6b4b056a68d"); //"head_rot" +const LLUUID ANIM_AGENT_PELVIS_FIX_ID = LLUUID("0c5dd2a2-514d-8893-d44d-05beffad208b"); //"pelvis_fix" +const LLUUID ANIM_AGENT_TARGET_ID = LLUUID("0e4896cb-fba4-926c-f355-8720189d5b55"); //"target" +const LLUUID ANIM_AGENT_WALK_ADJUST_ID = LLUUID("829bc85b-02fc-ec41-be2e-74cc6dd7215d"); //"walk_adjust" +// +// This must be in the same order as ANIM_AGENT_BODY_NOISE through ANIM_AGENT_WALK_ADJUST (see llmotion.h)! +static LLUUID const* lookup[] = { + &ANIM_AGENT_BODY_NOISE_ID, + &ANIM_AGENT_BREATHE_ROT_ID, + &ANIM_AGENT_PHYSICS_MOTION_ID, + &ANIM_AGENT_EDITING_ID, + &ANIM_AGENT_EYE_ID, + &ANIM_AGENT_FLY_ADJUST_ID, + &ANIM_AGENT_HAND_MOTION_ID, + &ANIM_AGENT_HEAD_ROT_ID, + &ANIM_AGENT_PELVIS_FIX_ID, + &ANIM_AGENT_TARGET_ID, + &ANIM_AGENT_WALK_ADJUST_ID +}; + +LLUUID const& mask2ID(U32 bit) +{ + int const lookupsize = sizeof(lookup) / sizeof(LLUUID const*); + int i = lookupsize - 1; + U32 mask = 1 << i; + for(;;) + { + if (bit == mask) + { + return *lookup[i]; + } + --i; + mask >>= 1; + llassert_always(i >= 0); + } +} + +#ifdef CWDEBUG +static char const* strlookup[] = { + "ANIM_AGENT_BODY_NOISE", + "ANIM_AGENT_BREATHE_ROT", + "ANIM_AGENT_PHYSICS_MOTION", + "ANIM_AGENT_EDITING", + "ANIM_AGENT_EYE", + "ANIM_AGENT_FLY_ADJUST", + "ANIM_AGENT_HAND_MOTION", + "ANIM_AGENT_HEAD_ROT", + "ANIM_AGENT_PELVIS_FIX", + "ANIM_AGENT_TARGET", + "ANIM_AGENT_WALK_ADJUST" +}; + +char const* mask2str(U32 bit) +{ + int const lookupsize = sizeof(lookup) / sizeof(LLUUID const*); + int i = lookupsize - 1; + U32 mask = 1 << i; + do + { + if (bit == mask) + { + return strlookup[i]; + } + --i; + mask >>= 1; + } + while(i >= 0); + return ""; +} +#endif + +// stopMotion(ANIM_AGENT_WALK_ADJUST) is called every frame, and for every avatar on the radar. +// That can be like 1000 times per second, so... speed that up a bit and lets not lookup the same LLUUID 1000 times +// per second in a std::map. Added the rest of the animations while I was at it. +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(); + } +} + +void LLVOAvatar::stopMotion(U32 bit, BOOL stop_immediate) +{ + if (isMotionActive(bit)) + { + stopMotion(mask2ID(bit), stop_immediate); + } +} +// //----------------------------------------------------------------------------- // Constants @@ -257,12 +345,12 @@ struct LLTextureMaskData // class LLBodyNoiseMotion //----------------------------------------------------------------------------- class LLBodyNoiseMotion : - public LLMotion + public AIMaskedMotion { public: // Constructor - LLBodyNoiseMotion(const LLUUID &id) - : LLMotion(id) + LLBodyNoiseMotion(LLUUID const& id, LLMotionController* controller) + : AIMaskedMotion(id, controller, ANIM_AGENT_BODY_NOISE) { mName = "body_noise"; mTorsoState = new LLJointState; @@ -277,7 +365,7 @@ public: //------------------------------------------------------------------------- // static constructor // all subclasses must implement such a function and register it - static LLMotion *create(const LLUUID &id) { return new LLBodyNoiseMotion(id); } + static LLMotion* create(LLUUID const& id, LLMotionController* controller) { return new LLBodyNoiseMotion(id, controller); } public: //------------------------------------------------------------------------- @@ -320,11 +408,6 @@ public: return STATUS_SUCCESS; } - // called when a motion is activated - // must return TRUE to indicate success, or else - // it will be deactivated - virtual BOOL onActivate() { return TRUE; } - // called per time step // must return TRUE while it is active, and // must return FALSE when the motion is completed. @@ -348,9 +431,6 @@ public: return TRUE; } - // called when a motion is deactivated - virtual void onDeactivate() {} - private: //------------------------------------------------------------------------- // joint states to be animated @@ -362,12 +442,12 @@ private: // class LLBreatheMotionRot //----------------------------------------------------------------------------- class LLBreatheMotionRot : - public LLMotion + public AIMaskedMotion { public: // Constructor - LLBreatheMotionRot(const LLUUID &id) : - LLMotion(id), + LLBreatheMotionRot(LLUUID const& id, LLMotionController* controller) : + AIMaskedMotion(id, controller, ANIM_AGENT_BREATHE_ROT), mBreatheRate(1.f), mCharacter(NULL) { @@ -384,7 +464,7 @@ public: //------------------------------------------------------------------------- // static constructor // all subclasses must implement such a function and register it - static LLMotion *create(const LLUUID &id) { return new LLBreatheMotionRot(id); } + static LLMotion* create(LLUUID const& id, LLMotionController* controller) { return new LLBreatheMotionRot(id, controller); } public: //------------------------------------------------------------------------- @@ -437,11 +517,6 @@ public: } } - // called when a motion is activated - // must return TRUE to indicate success, or else - // it will be deactivated - virtual BOOL onActivate() { return TRUE; } - // called per time step // must return TRUE while it is active, and // must return FALSE when the motion is completed. @@ -456,9 +531,6 @@ public: return TRUE; } - // called when a motion is deactivated - virtual void onDeactivate() {} - private: //------------------------------------------------------------------------- // joint states to be animated @@ -472,12 +544,12 @@ private: // class LLPelvisFixMotion //----------------------------------------------------------------------------- class LLPelvisFixMotion : - public LLMotion + public AIMaskedMotion { public: // Constructor - LLPelvisFixMotion(const LLUUID &id) - : LLMotion(id), mCharacter(NULL) + LLPelvisFixMotion(LLUUID const& id, LLMotionController* controller) + : AIMaskedMotion(id, controller, ANIM_AGENT_PELVIS_FIX), mCharacter(NULL) { mName = "pelvis_fix"; @@ -493,7 +565,7 @@ public: //------------------------------------------------------------------------- // static constructor // all subclasses must implement such a function and register it - static LLMotion *create(const LLUUID& id) { return new LLPelvisFixMotion(id); } + static LLMotion* create(LLUUID const& id, LLMotionController* controller) { return new LLPelvisFixMotion(id, controller); } public: //------------------------------------------------------------------------- @@ -538,11 +610,6 @@ public: return STATUS_SUCCESS; } - // called when a motion is activated - // must return TRUE to indicate success, or else - // it will be deactivated - virtual BOOL onActivate() { return TRUE; } - // called per time step // must return TRUE while it is active, and // must return FALSE when the motion is completed. @@ -553,9 +620,6 @@ public: return TRUE; } - // called when a motion is deactivated - virtual void onDeactivate() {} - private: //------------------------------------------------------------------------- // joint states to be animated @@ -1438,17 +1502,17 @@ void LLVOAvatar::deleteCachedImages(bool clearAll) //------------------------------------------------------------------------ void LLVOAvatar::initClass() { - gAnimLibrary.animStateSetString(ANIM_AGENT_BODY_NOISE,"body_noise"); - gAnimLibrary.animStateSetString(ANIM_AGENT_BREATHE_ROT,"breathe_rot"); - gAnimLibrary.animStateSetString(ANIM_AGENT_PHYSICS_MOTION,"physics_motion"); - gAnimLibrary.animStateSetString(ANIM_AGENT_EDITING,"editing"); - gAnimLibrary.animStateSetString(ANIM_AGENT_EYE,"eye"); - gAnimLibrary.animStateSetString(ANIM_AGENT_FLY_ADJUST,"fly_adjust"); - gAnimLibrary.animStateSetString(ANIM_AGENT_HAND_MOTION,"hand_motion"); - gAnimLibrary.animStateSetString(ANIM_AGENT_HEAD_ROT,"head_rot"); - gAnimLibrary.animStateSetString(ANIM_AGENT_PELVIS_FIX,"pelvis_fix"); - gAnimLibrary.animStateSetString(ANIM_AGENT_TARGET,"target"); - gAnimLibrary.animStateSetString(ANIM_AGENT_WALK_ADJUST,"walk_adjust"); + gAnimLibrary.animStateSetString(ANIM_AGENT_BODY_NOISE_ID,"body_noise"); + gAnimLibrary.animStateSetString(ANIM_AGENT_BREATHE_ROT_ID,"breathe_rot"); + gAnimLibrary.animStateSetString(ANIM_AGENT_PHYSICS_MOTION_ID,"physics_motion"); + gAnimLibrary.animStateSetString(ANIM_AGENT_EDITING_ID,"editing"); + gAnimLibrary.animStateSetString(ANIM_AGENT_EYE_ID,"eye"); + gAnimLibrary.animStateSetString(ANIM_AGENT_FLY_ADJUST_ID,"fly_adjust"); + gAnimLibrary.animStateSetString(ANIM_AGENT_HAND_MOTION_ID,"hand_motion"); + gAnimLibrary.animStateSetString(ANIM_AGENT_HEAD_ROT_ID,"head_rot"); + gAnimLibrary.animStateSetString(ANIM_AGENT_PELVIS_FIX_ID,"pelvis_fix"); + gAnimLibrary.animStateSetString(ANIM_AGENT_TARGET_ID,"target"); + gAnimLibrary.animStateSetString(ANIM_AGENT_WALK_ADJUST_ID,"walk_adjust"); SHClientTagMgr::instance(); //Instantiate. Parse. Will fetch a new tag file if AscentUpdateTagsOnLoad is true. } @@ -1506,19 +1570,19 @@ void LLVOAvatar::initInstance(void) registerMotion( ANIM_AGENT_WALK_NEW, LLKeyframeWalkMotion::create ); //v2 // motions without a start/stop bit - registerMotion( ANIM_AGENT_BODY_NOISE, LLBodyNoiseMotion::create ); - registerMotion( ANIM_AGENT_BREATHE_ROT, LLBreatheMotionRot::create ); - registerMotion( ANIM_AGENT_PHYSICS_MOTION, LLPhysicsMotionController::create ); - registerMotion( ANIM_AGENT_EDITING, LLEditingMotion::create ); - registerMotion( ANIM_AGENT_EYE, LLEyeMotion::create ); - registerMotion( ANIM_AGENT_FLY_ADJUST, LLFlyAdjustMotion::create ); - registerMotion( ANIM_AGENT_HAND_MOTION, LLHandMotion::create ); - registerMotion( ANIM_AGENT_HEAD_ROT, LLHeadRotMotion::create ); - registerMotion( ANIM_AGENT_PELVIS_FIX, LLPelvisFixMotion::create ); - registerMotion( ANIM_AGENT_SIT_FEMALE, LLKeyframeMotion::create ); - registerMotion( ANIM_AGENT_TARGET, LLTargetingMotion::create ); - registerMotion( ANIM_AGENT_WALK_ADJUST, LLWalkAdjustMotion::create ); + registerMotion( ANIM_AGENT_BODY_NOISE_ID, LLBodyNoiseMotion::create ); + registerMotion( ANIM_AGENT_BREATHE_ROT_ID, LLBreatheMotionRot::create ); + registerMotion( ANIM_AGENT_PHYSICS_MOTION_ID, LLPhysicsMotionController::create ); + registerMotion( ANIM_AGENT_EDITING_ID, LLEditingMotion::create ); + registerMotion( ANIM_AGENT_EYE_ID, LLEyeMotion::create ); + registerMotion( ANIM_AGENT_FLY_ADJUST_ID, LLFlyAdjustMotion::create ); + registerMotion( ANIM_AGENT_HAND_MOTION_ID, LLHandMotion::create ); + registerMotion( ANIM_AGENT_HEAD_ROT_ID, LLHeadRotMotion::create ); + registerMotion( ANIM_AGENT_PELVIS_FIX_ID, LLPelvisFixMotion::create ); + registerMotion( ANIM_AGENT_TARGET_ID, LLTargetingMotion::create ); + registerMotion( ANIM_AGENT_WALK_ADJUST_ID, LLWalkAdjustMotion::create ); + registerMotion( ANIM_AGENT_SIT_FEMALE, LLKeyframeMotion::create ); } LLAvatarAppearance::initInstance(); @@ -3748,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) { @@ -3767,6 +3832,14 @@ BOOL LLVOAvatar::updateCharacter(LLAgent &agent) motionp->getName().c_str(), (U32)motionp->getPriority()); } + if (motionp->server()) + { +#ifdef SHOW_ASSERT + output += llformat(" rt=%.1f r=%d s=0x%xl", motionp->getRuntime(), motionp->mReadyEvents, motionp->server()); +#else + output += llformat(" rt=%.1f s=0x%xl", motionp->getRuntime(), motionp->server()); +#endif + } addDebugText(output); } } @@ -3884,7 +3957,7 @@ BOOL LLVOAvatar::updateCharacter(LLAgent &agent) getOffObject(); // //Singu note: this appears to be a safety catch: - // when getParent() is NULL and we're note playing ANIM_AGENT_SIT_GROUND_CONSTRAINED then we aren't sitting! + // when getParent() is NULL and we're not playing ANIM_AGENT_SIT_GROUND_CONSTRAINED then we aren't sitting! // The previous call existed in an attempt to fix this inconsistent state by standing up from an object. // However, since getParent() is NULL that function would crash! // Since we never got crash reports regarding to this, that apparently never happened, except, I discovered @@ -5437,6 +5510,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();) { @@ -5446,9 +5520,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 } @@ -5478,7 +5552,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 } diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index 891fae7d9..ded74c50b 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -54,6 +54,9 @@ #include "llavatarname.h" +// +#if 0 +// Hide these: should be using the bit masks everywhere. extern const LLUUID ANIM_AGENT_BODY_NOISE; extern const LLUUID ANIM_AGENT_BREATHE_ROT; extern const LLUUID ANIM_AGENT_PHYSICS_MOTION; @@ -65,6 +68,8 @@ extern const LLUUID ANIM_AGENT_HEAD_ROT; extern const LLUUID ANIM_AGENT_PELVIS_FIX; extern const LLUUID ANIM_AGENT_TARGET; extern const LLUUID ANIM_AGENT_WALK_ADJUST; +#endif +// class LLAPRFile; class LLViewerWearable; @@ -230,6 +235,10 @@ public: /*virtual*/ LLUUID remapMotionID(const LLUUID& id); /*virtual*/ BOOL startMotion(const LLUUID& id, F32 time_offset = 0.f); /*virtual*/ BOOL stopMotion(const LLUUID& id, BOOL stop_immediate = FALSE); + // + void startMotion(U32 bit, F32 start_offset = 0.f); + void stopMotion(U32 bit, BOOL stop_immediate = FALSE); + // virtual void stopMotionFromSource(const LLUUID& source_id); virtual void requestStopMotion(LLMotion* motion); LLMotion* findMotion(const LLUUID& id) const; diff --git a/indra/newview/llvoavatarself.cpp b/indra/newview/llvoavatarself.cpp index 6f0a4bd9f..c3ead99db 100644 --- a/indra/newview/llvoavatarself.cpp +++ b/indra/newview/llvoavatarself.cpp @@ -897,9 +897,9 @@ void LLVOAvatarSelf::updateRegion(LLViewerRegion *regionp) //virtual void LLVOAvatarSelf::idleUpdateTractorBeam() { - - - if(gSavedSettings.getBOOL("DisablePointAtAndBeam")) + // + static LLCachedControl disable_pointat_effect("DisablePointAtAndBeam"); + if (disable_pointat_effect) { return; } diff --git a/indra/newview/skins/default/xui/en-us/panel_preferences_ascent_system.xml b/indra/newview/skins/default/xui/en-us/panel_preferences_ascent_system.xml index e4d6537c0..72a5362fc 100644 --- a/indra/newview/skins/default/xui/en-us/panel_preferences_ascent_system.xml +++ b/indra/newview/skins/default/xui/en-us/panel_preferences_ascent_system.xml @@ -83,9 +83,9 @@ - + - +