From da87e8bd370ea079576f8b412a4ddb80c0715bd1 Mon Sep 17 00:00:00 2001
From: leviathan <leviathan@lindenlab.com>
Date: Fri, 13 Sep 2024 10:47:24 -0700
Subject: send AgentUpdate ASAP when control bits change

---
 indra/newview/llagent.cpp         |  54 ++-----
 indra/newview/llagent.h           |   4 -
 indra/newview/llagentcamera.cpp   |   4 -
 indra/newview/llappviewer.cpp     |  38 +----
 indra/newview/llappviewer.h       |   6 -
 indra/newview/llviewermessage.cpp | 333 +++++++++++++++++---------------------
 6 files changed, 171 insertions(+), 268 deletions(-)

diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp
index 7c37cc1c00..c8b0adbaf8 100644
--- a/indra/newview/llagent.cpp
+++ b/indra/newview/llagent.cpp
@@ -439,8 +439,6 @@ LLAgent::LLAgent() :
     mIsDoNotDisturb(false),
 
     mControlFlags(0x00000000),
-    mbFlagsDirty(false),
-    mbFlagsNeedReset(false),
 
     mAutoPilot(false),
     mAutoPilotFlyOnStop(false),
@@ -936,8 +934,6 @@ void LLAgent::setFlying(bool fly, bool fail_sound)
 
     // Update Movement Controls according to Fly mode
     LLFloaterMove::setFlyingMode(fly);
-
-    mbFlagsDirty = true;
 }
 
 
@@ -1068,7 +1064,6 @@ void LLAgent::setRegion(LLViewerRegion *regionp)
             {
                 regionp->setCapabilitiesReceivedCallback(LLAgent::capabilityReceivedCallback);
             }
-
         }
         else
         {
@@ -1556,7 +1551,6 @@ U32 LLAgent::getControlFlags()
 void LLAgent::setControlFlags(U32 mask)
 {
     mControlFlags |= mask;
-    mbFlagsDirty = true;
 }
 
 
@@ -1565,28 +1559,7 @@ void LLAgent::setControlFlags(U32 mask)
 //-----------------------------------------------------------------------------
 void LLAgent::clearControlFlags(U32 mask)
 {
-    U32 old_flags = mControlFlags;
     mControlFlags &= ~mask;
-    if (old_flags != mControlFlags)
-    {
-        mbFlagsDirty = true;
-    }
-}
-
-//-----------------------------------------------------------------------------
-// controlFlagsDirty()
-//-----------------------------------------------------------------------------
-bool LLAgent::controlFlagsDirty() const
-{
-    return mbFlagsDirty;
-}
-
-//-----------------------------------------------------------------------------
-// enableControlFlagReset()
-//-----------------------------------------------------------------------------
-void LLAgent::enableControlFlagReset()
-{
-    mbFlagsNeedReset = true;
 }
 
 //-----------------------------------------------------------------------------
@@ -1594,14 +1567,9 @@ void LLAgent::enableControlFlagReset()
 //-----------------------------------------------------------------------------
 void LLAgent::resetControlFlags()
 {
-    if (mbFlagsNeedReset)
-    {
-        mbFlagsNeedReset = false;
-        mbFlagsDirty = false;
-        // reset all of the ephemeral flags
-        // some flags are managed elsewhere
-        mControlFlags &= AGENT_CONTROL_AWAY | AGENT_CONTROL_FLY | AGENT_CONTROL_MOUSELOOK;
-    }
+    // reset all of the ephemeral flags
+    // some flags are managed elsewhere
+    mControlFlags &= AGENT_CONTROL_AWAY | AGENT_CONTROL_FLY | AGENT_CONTROL_MOUSELOOK;
 }
 
 //-----------------------------------------------------------------------------
@@ -2085,11 +2053,19 @@ void LLAgent::propagate(const F32 dt)
     }
 
     // handle rotation based on keyboard levels
-    const F32 YAW_RATE = 90.f * DEG_TO_RAD;             // radians per second
-    yaw(YAW_RATE * gAgentCamera.getYawKey() * dt);
+    constexpr F32 YAW_RATE = 90.f * DEG_TO_RAD;                // radians per second
+    F32 angle = YAW_RATE * gAgentCamera.getYawKey() * dt;
+    if (fabs(angle) > 0.0f)
+    {
+        yaw(angle);
+    }
 
-    const F32 PITCH_RATE = 90.f * DEG_TO_RAD;           // radians per second
-    pitch(PITCH_RATE * gAgentCamera.getPitchKey() * dt);
+    constexpr F32 PITCH_RATE = 90.f * DEG_TO_RAD;            // radians per second
+    angle = PITCH_RATE * gAgentCamera.getPitchKey() * dt;
+    if (fabs(angle) > 0.0f)
+    {
+        pitch(angle);
+    }
 
     // handle auto-land behavior
     if (isAgentAvatarValid())
diff --git a/indra/newview/llagent.h b/indra/newview/llagent.h
index 7c7f7aa91d..afc34f747f 100644
--- a/indra/newview/llagent.h
+++ b/indra/newview/llagent.h
@@ -482,21 +482,17 @@ public:
     void            setControlFlags(U32 mask);      // Performs bitwise mControlFlags |= mask
     void            clearControlFlags(U32 mask);    // Performs bitwise mControlFlags &= ~mask
     bool            controlFlagsDirty() const;
-    void            enableControlFlagReset();
     void            resetControlFlags();
     bool            anyControlGrabbed() const;      // True iff a script has taken over a control
     bool            isControlGrabbed(S32 control_index) const;
     // Send message to simulator to force grabbed controls to be
     // released, in case of a poorly written script.
     void            forceReleaseControls();
-    void            setFlagsDirty() { mbFlagsDirty = true; }
 
 private:
     S32             mControlsTakenCount[TOTAL_CONTROLS];
     S32             mControlsTakenPassedOnCount[TOTAL_CONTROLS];
     U32             mControlFlags;                  // Replacement for the mFooKey's
-    bool            mbFlagsDirty;
-    bool            mbFlagsNeedReset;               // ! HACK ! For preventing incorrect flags sent when crossing region boundaries
 
     //--------------------------------------------------------------------
     // Animations
diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp
index e7531f963b..81e79a2ed9 100644
--- a/indra/newview/llagentcamera.cpp
+++ b/indra/newview/llagentcamera.cpp
@@ -2204,10 +2204,6 @@ void LLAgentCamera::changeCameraToMouselook(bool animate)
         mCameraMode = CAMERA_MODE_MOUSELOOK;
         const U32 old_flags = gAgent.getControlFlags();
         gAgent.setControlFlags(AGENT_CONTROL_MOUSELOOK);
-        if (old_flags != gAgent.getControlFlags())
-        {
-            gAgent.setFlagsDirty();
-        }
 
         if (animate)
         {
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 4eb4f5ae20..764e52accb 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -642,8 +642,6 @@ LLAppViewer::LLAppViewer()
     mQuitRequested(false),
     mClosingFloaters(false),
     mLogoutRequestSent(false),
-    mLastAgentControlFlags(0),
-    mLastAgentForceUpdate(0),
     mMainloopTimeout(NULL),
     mAgentRegionLastAlive(false),
     mRandomizeFramerate(LLCachedControl<bool>(gSavedSettings,"Randomize Framerate", false)),
@@ -4711,30 +4709,13 @@ void LLAppViewer::idle()
             gAgent.autoPilot(&yaw);
         }
 
-        static LLFrameTimer agent_update_timer;
-
-        // When appropriate, update agent location to the simulator.
-        F32 agent_update_time = agent_update_timer.getElapsedTimeF32();
-        F32 agent_force_update_time = mLastAgentForceUpdate + agent_update_time;
-        bool timed_out = agent_update_time > (1.0f / (F32)AGENT_UPDATES_PER_SECOND);
-        bool force_send =
-            // if there is something to send
-            (gAgent.controlFlagsDirty() && timed_out)
-            // if something changed
-            || (mLastAgentControlFlags != gAgent.getControlFlags())
-            // keep alive
-            || (agent_force_update_time > (1.0f / (F32) AGENT_FORCE_UPDATES_PER_SECOND));
-        // timing out doesn't warranty that an update will be sent,
-        // just that it will be checked.
-        if (force_send || timed_out)
-        {
-            LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
-            // Send avatar and camera info
-            mLastAgentControlFlags = gAgent.getControlFlags();
-            mLastAgentForceUpdate = force_send ? 0 : agent_force_update_time;
-            send_agent_update(force_send);
-            agent_update_timer.reset();
-        }
+        send_agent_update(false);
+
+        // After calling send_agent_update() in the mainloop we always clear
+        // the agent's ephemeral ControlFlags (whether an AgentUpdate was
+        // actually sent or not) because these will be recomputed based on
+        // real-time key/controller input and resubmitted next frame.
+        gAgent.resetControlFlags();
     }
 
     //////////////////////////////////////
@@ -5346,11 +5327,6 @@ void LLAppViewer::idleNetwork()
         }
 #endif
 
-
-
-        // we want to clear the control after sending out all necessary agent updates
-        gAgent.resetControlFlags();
-
         // Decode enqueued messages...
         S32 remaining_possible_decodes = MESSAGE_MAX_PER_FRAME - total_decoded;
 
diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h
index 7b456cc542..4ce4259ed8 100644
--- a/indra/newview/llappviewer.h
+++ b/indra/newview/llappviewer.h
@@ -322,8 +322,6 @@ private:
     bool mQuitRequested;                // User wants to quit, may have modified documents open.
     bool mClosingFloaters;
     bool mLogoutRequestSent;            // Disconnect message sent to simulator, no longer safe to send messages to the sim.
-    U32 mLastAgentControlFlags;
-    F32 mLastAgentForceUpdate;
     struct SettingsFiles* mSettingsLocationList;
 
     LLWatchdogTimeout* mMainloopTimeout;
@@ -341,10 +339,6 @@ private:
     bool mIsFirstRun;
 };
 
-// consts from viewer.h
-const S32 AGENT_UPDATES_PER_SECOND  = 125; // Value derived experimentally to avoid Input Delays with latest PBR-Capable Viewers when viewer FPS is highly volatile.
-const S32 AGENT_FORCE_UPDATES_PER_SECOND  = 1;
-
 // Globals with external linkage. From viewer.h
 // *NOTE:Mani - These will be removed as the Viewer App Cleanup project continues.
 //
diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp
index 872a9a1581..0861b439eb 100644
--- a/indra/newview/llviewermessage.cpp
+++ b/indra/newview/llviewermessage.cpp
@@ -3139,14 +3139,9 @@ void process_crossed_region(LLMessageSystem* msg, void**)
 }
 
 
-
-// Sends avatar and camera information to simulator.
-// Sent roughly once per frame, or 20 times per second, whichever is less often
-
-const F32 THRESHOLD_HEAD_ROT_QDOT = 0.9997f;    // ~= 2.5 degrees -- if its less than this we need to update head_rot
-const F32 MAX_HEAD_ROT_QDOT = 0.99999f;         // ~= 0.5 degrees -- if its greater than this then no need to update head_rot
-                                                // between these values we delay the updates (but no more than one second)
-
+// sends an AgentUpdate message to the server... or not:
+// only when force_send is 'true' OR
+// something changed AND the update is not being throttled
 void send_agent_update(bool force_send, bool send_reliable)
 {
     LL_PROFILE_ZONE_SCOPED;
@@ -3154,72 +3149,46 @@ void send_agent_update(bool force_send, bool send_reliable)
 
     if (gAgent.getTeleportState() != LLAgent::TELEPORT_NONE)
     {
-        // We don't care if they want to send an agent update, they're not allowed to until the simulator
-        // that's the target is ready to receive them (after avatar_init_complete is received)
+        // We don't care if they want to send an agent update, they're not allowed
+        // until the target simulator is ready to receive them
+        // (e.g. after avatar_init_complete is received)
         return;
     }
 
-    // We have already requested to log out.  Don't send agent updates.
-    if(LLAppViewer::instance()->logoutRequestSent())
+    if (LLAppViewer::instance()->logoutRequestSent())
     {
+        // We have already requested to log out.  Don't send agent updates.
         return;
     }
 
-    // no region to send update to
-    if(gAgent.getRegion() == NULL)
+    if (gAgent.getRegion() == nullptr || gDisconnected)
     {
+        // no region to send update to
         return;
     }
 
-    const F32 TRANSLATE_THRESHOLD = 0.01f;
-
-    // NOTA BENE: This is (intentionally?) using the small angle sine approximation to test for rotation
-    //            Plus, there is an extra 0.5 in the mix since the perpendicular between last_camera_at and getAtAxis() bisects cam_rot_change
-    //            Thus, we're actually testing against 0.2 degrees
-    const F32 ROTATION_THRESHOLD = 0.1f * 2.f*F_PI/360.f;           //  Rotation thresh 0.2 deg, see note above
-
-    const U8 DUP_MSGS = 1;              //  HACK!  number of times to repeat data on motionless agent
-
-    //  Store data on last sent update so that if no changes, no send
-    static LLVector3 last_camera_pos_agent,
-                     last_camera_at,
-                     last_camera_left,
-                     last_camera_up;
-
-    static LLVector3 cam_center_chg,
-                     cam_rot_chg;
-
-    static LLQuaternion last_head_rot;
-    static U32 last_control_flags = 0;
-    static U8 last_render_state;
-    static U8 duplicate_count = 0;
-    static F32 head_rot_chg = 1.0;
-    static U8 last_flags;
-
-    LLMessageSystem *msg = gMessageSystem;
-    LLVector3       camera_pos_agent;               // local to avatar's region
-    U8              render_state;
-
-    LLQuaternion body_rotation = gAgent.getFrameAgent().getQuaternion();
-    LLQuaternion head_rotation = gAgent.getHeadRotation();
-
-    camera_pos_agent = gAgentCamera.getCameraPositionAgent();
+    static F64          last_send_time = 0.0;
+    static U32          last_control_flags = 0;
+    static U8           last_render_state = 0;
+    static U8           last_flags = AU_FLAGS_NONE;
+    static LLQuaternion last_body_rot,
+                        last_head_rot;
+    static LLVector3    last_camera_pos_agent,
+                        last_camera_at;
 
-    render_state = gAgent.getRenderState();
-
-    U32     control_flag_change = 0;
-    U8      flag_change = 0;
-
-    cam_center_chg = last_camera_pos_agent - camera_pos_agent;
-    cam_rot_chg = last_camera_at - LLViewerCamera::getInstance()->getAtAxis();
+    // compute sec_since_last_send
+    constexpr F64 MAX_AGENT_UPDATES_PER_SECOND = 125.0; // Value derived experimentally to avoid Input Delays with latest PBR-Capable Viewers when viewer FPS is highly volatile.
+    constexpr F64 MIN_AGENT_UPDATES_PER_SECOND = 1.0; // keep-alive rate
+    constexpr F64 MIN_AGENT_UPDATE_PERIOD = 1.0 / MAX_AGENT_UPDATES_PER_SECOND;
+    constexpr F64 MAX_AGENT_UPDATE_PERIOD = 1.0 / MIN_AGENT_UPDATES_PER_SECOND;
+    F64 now =  LLFrameTimer::getTotalSeconds();
+    F64 sec_since_last_send = now - last_send_time;
 
     // If a modifier key is held down, turn off
     // LBUTTON and ML_LBUTTON so that using the camera (alt-key) doesn't
     // trigger a control event.
     U32 control_flags = gAgent.getControlFlags();
-
-    MASK    key_mask = gKeyboard->currentMask(true);
-
+    MASK key_mask = gKeyboard->currentMask(true);
     if (key_mask & MASK_ALT || key_mask & MASK_CONTROL)
     {
         control_flags &= ~( AGENT_CONTROL_LBUTTON_DOWN |
@@ -3228,7 +3197,22 @@ void send_agent_update(bool force_send, bool send_reliable)
                             AGENT_CONTROL_ML_LBUTTON_UP ;
     }
 
-    control_flag_change = last_control_flags ^ control_flags;
+    // any change in control_flags should be sent ASAP, so we fold that into force_send
+    force_send = force_send || (control_flags != last_control_flags);
+
+    if (! force_send && sec_since_last_send < MIN_AGENT_UPDATE_PERIOD)
+    {
+        // throttle less-important AgentUpdates
+        return;
+    }
+
+    bool send_update = force_send || sec_since_last_send > MAX_AGENT_UPDATE_PERIOD;
+
+    LLVector3 camera_pos_agent = gAgentCamera.getCameraPositionAgent(); // local to avatar's region
+    LLVector3 camera_at = LLViewerCamera::getInstance()->getAtAxis();
+    LLQuaternion body_rotation = gAgent.getFrameAgent().getQuaternion();
+    LLQuaternion head_rotation = gAgent.getHeadRotation();
+    U8 render_state = gAgent.getRenderState();
 
     U8 flags = AU_FLAGS_NONE;
     if (gAgent.isGroupTitleHidden())
@@ -3240,159 +3224,140 @@ void send_agent_update(bool force_send, bool send_reliable)
         flags |= AU_FLAGS_CLIENT_AUTOPILOT;
     }
 
-    flag_change = last_flags ^ flags;
-
-    head_rot_chg = dot(last_head_rot, head_rotation);
-
-    //static S32 msg_number = 0;        // Used for diagnostic log messages
-
-    if (force_send ||
-        (cam_center_chg.magVec() > TRANSLATE_THRESHOLD) ||
-        (head_rot_chg < THRESHOLD_HEAD_ROT_QDOT) ||
-        (last_render_state != render_state) ||
-        (cam_rot_chg.magVec() > ROTATION_THRESHOLD) ||
-        control_flag_change != 0 ||
-        flag_change != 0)
+    if (!send_update)
     {
-        /* Diagnotics to show why we send the AgentUpdate message.  Also un-commment the msg_number code above and below this block
-        msg_number += 1;
-        if (head_rot_chg < THRESHOLD_HEAD_ROT_QDOT)
-        {
-            //LL_INFOS("Messaging") << "head rot " << head_rotation << LL_ENDL;
-            LL_INFOS("Messaging") << "msg " << msg_number << ", frame " << LLFrameTimer::getFrameCount() << ", head_rot_chg " << head_rot_chg << LL_ENDL;
-        }
-        if (cam_rot_chg.magVec() > ROTATION_THRESHOLD)
-        {
-            LL_INFOS("Messaging") << "msg " << msg_number << ", frame " << LLFrameTimer::getFrameCount() << ", cam rot " <<  cam_rot_chg.magVec() << LL_ENDL;
-        }
-        if (cam_center_chg.magVec() > TRANSLATE_THRESHOLD)
-        {
-            LL_INFOS("Messaging") << "msg " << msg_number << ", frame " << LLFrameTimer::getFrameCount() << ", cam center " << cam_center_chg.magVec() << LL_ENDL;
-        }
-//      if (drag_delta_chg.magVec() > TRANSLATE_THRESHOLD)
-//      {
-//          LL_INFOS("Messaging") << "drag delta " << drag_delta_chg.magVec() << LL_ENDL;
-//      }
-        if (control_flag_change)
+        // check to see if anything changed
+        // use a do-while-false to provide easy way to break out as soon as we find something changed
+        do
         {
-            LL_INFOS("Messaging") << "msg " << msg_number << ", frame " << LLFrameTimer::getFrameCount() << ", dcf = " << control_flag_change << LL_ENDL;
-        }
-*/
-
-        duplicate_count = 0;
-    }
-    else
-    {
-        duplicate_count++;
+            // start with the easy evaluations and progress to more complicated
 
-        if (head_rot_chg < MAX_HEAD_ROT_QDOT  &&  duplicate_count < AGENT_UPDATES_PER_SECOND)
-        {
-            // The head_rotation is sent for updating things like attached guns.
-            // We only trigger a new update when head_rotation deviates beyond
-            // some threshold from the last update, however this can break fine
-            // adjustments when trying to aim an attached gun, so what we do here
-            // (where we would normally skip sending an update when nothing has changed)
-            // is gradually reduce the threshold to allow a better update to
-            // eventually get sent... should update to within 0.5 degrees in less
-            // than a second.
-            if (head_rot_chg < THRESHOLD_HEAD_ROT_QDOT + (MAX_HEAD_ROT_QDOT - THRESHOLD_HEAD_ROT_QDOT) * duplicate_count / AGENT_UPDATES_PER_SECOND)
+            // check render_state
+            if (last_render_state != render_state)
             {
-                duplicate_count = 0;
+                send_update = true;
+                break;
             }
-            else
+
+            // check flags
+            if (last_flags != flags)
             {
-                return;
+                send_update = true;
+                break;
             }
-        }
-        else
-        {
-            return;
-        }
-    }
 
-    if (duplicate_count < DUP_MSGS && !gDisconnected)
-    {
-        /* More diagnostics to count AgentUpdate messages
-        static S32 update_sec = 0;
-        static S32 update_count = 0;
-        static S32 max_update_count = 0;
-        S32 cur_sec = lltrunc( LLTimer::getTotalSeconds() );
-        update_count += 1;
-        if (cur_sec != update_sec)
-        {
-            if (update_sec != 0)
+            // check translation
+            constexpr F32 TRANSLATE_THRESHOLD = 0.01f;
+            if ((last_camera_pos_agent - camera_pos_agent).magVec() > TRANSLATE_THRESHOLD)
             {
-                update_sec = cur_sec;
-                //msg_number = 0;
-                max_update_count = llmax(max_update_count, update_count);
-                LL_INFOS() << "Sent " << update_count << " AgentUpdate messages per second, max is " << max_update_count << LL_ENDL;
+                send_update = true;
+                break;
             }
-            update_sec = cur_sec;
-            update_count = 0;
-        }
-        */
 
-        // Build the message
-        msg->newMessageFast(_PREHASH_AgentUpdate);
-        msg->nextBlockFast(_PREHASH_AgentData);
-        msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
-        msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
-        msg->addQuatFast(_PREHASH_BodyRotation, body_rotation);
-        msg->addQuatFast(_PREHASH_HeadRotation, head_rotation);
-        msg->addU8Fast(_PREHASH_State, render_state);
-        msg->addU8Fast(_PREHASH_Flags, flags);
-
-//      if (camera_pos_agent.mV[VY] > 255.f)
-//      {
-//          LL_INFOS("Messaging") << "Sending camera center " << camera_pos_agent << LL_ENDL;
-//      }
-
-        msg->addVector3Fast(_PREHASH_CameraCenter, camera_pos_agent);
-        msg->addVector3Fast(_PREHASH_CameraAtAxis, LLViewerCamera::getInstance()->getAtAxis());
-        msg->addVector3Fast(_PREHASH_CameraLeftAxis, LLViewerCamera::getInstance()->getLeftAxis());
-        msg->addVector3Fast(_PREHASH_CameraUpAxis, LLViewerCamera::getInstance()->getUpAxis());
-        msg->addF32Fast(_PREHASH_Far, gAgentCamera.mDrawDistance);
+            // check camera rotation
+            // Note: we are using the sine small angle approximation trick here
+            constexpr F32 RADIANS_PER_DEGREE = F_PI / 360.f;
+            constexpr F32 CAMERA_AT_THRESHOLD = 0.2f * RADIANS_PER_DEGREE;
+            if ((last_camera_at - camera_at).magVec() > CAMERA_AT_THRESHOLD)
+            {
+                send_update = true;
+                break;
+            }
 
-        msg->addU32Fast(_PREHASH_ControlFlags, control_flags);
+            // check head rotation
+            constexpr F64 MIN_HEAD_ROT_QDOT = 0.9997;    // ~= 2.5 degrees -- if its less than this we need to update head_rot
+            constexpr F64 MAX_HEAD_ROT_QDOT = 0.99999;   // ~= 0.5 degrees -- if its greater than this then we consider it close enough
 
-        if (gDebugClicks)
-        {
-            if (control_flags & AGENT_CONTROL_LBUTTON_DOWN)
+            if (fabs((F64)(dot(last_body_rot, body_rotation))) < MIN_HEAD_ROT_QDOT)
             {
-                LL_INFOS("Messaging") << "AgentUpdate left button down" << LL_ENDL;
+                send_update = true;
+                break;
             }
 
-            if (control_flags & AGENT_CONTROL_LBUTTON_UP)
+            F64 head_rot_qdot = fabs((F64)(dot(last_head_rot, head_rotation)));
+            if (head_rot_qdot > MAX_HEAD_ROT_QDOT)
             {
-                LL_INFOS("Messaging") << "AgentUpdate left button up" << LL_ENDL;
+                // close enough
+                return;
+            }
+            else if (head_rot_qdot < MIN_HEAD_ROT_QDOT)
+            {
+                // way off
+                send_update = true;
+                break;
             }
-        }
 
-        gAgent.enableControlFlagReset();
+            // Finally, if we get here then head_rot_qdot is somewhere between MIN_ and MAX_HEAD_ROT_QDOT
 
-        if (!send_reliable)
+            // The head_rotation is sent for updating things like attached guns.
+            // We only trigger a new update when head_rotation deviates beyond
+            // some threshold from the last update, however this can break fine
+            // adjustments when trying to aim an attached gun, so what we do here
+            // (where we would normally skip sending an update when nothing has changed)
+            // is linearly increase the min threshold until an update is sent.
+            // Min threshold should update to MAX_HEAD_ROT_QDOT within THRESHOLD_GROWTH_PERIOD.
+            constexpr F64 THRESHOLD_GROWTH_PERIOD = 0.5;
+            constexpr F64 threshold_growth_per_sec = (MAX_HEAD_ROT_QDOT - MIN_HEAD_ROT_QDOT) / THRESHOLD_GROWTH_PERIOD;
+            send_update = head_rot_qdot < MIN_HEAD_ROT_QDOT + sec_since_last_send * threshold_growth_per_sec;
+        } while (false);
+    }
+
+    if (!send_update)
+    {
+        return;
+    }
+
+    // Build the message
+    LLMessageSystem* msg = gMessageSystem;
+    msg->newMessageFast(_PREHASH_AgentUpdate);
+    msg->nextBlockFast(_PREHASH_AgentData);
+    msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
+    msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
+    msg->addQuatFast(_PREHASH_BodyRotation, body_rotation);
+    msg->addQuatFast(_PREHASH_HeadRotation, head_rotation);
+    msg->addU8Fast(_PREHASH_State, render_state);
+    msg->addU8Fast(_PREHASH_Flags, flags);
+
+    msg->addVector3Fast(_PREHASH_CameraCenter, camera_pos_agent);
+    msg->addVector3Fast(_PREHASH_CameraAtAxis, camera_at);
+    msg->addVector3Fast(_PREHASH_CameraLeftAxis, LLViewerCamera::getInstance()->getLeftAxis());
+    msg->addVector3Fast(_PREHASH_CameraUpAxis, LLViewerCamera::getInstance()->getUpAxis());
+    msg->addF32Fast(_PREHASH_Far, gAgentCamera.mDrawDistance);
+
+    msg->addU32Fast(_PREHASH_ControlFlags, control_flags);
+
+    if (gDebugClicks)
+    {
+        if (control_flags & AGENT_CONTROL_LBUTTON_DOWN)
         {
-            gAgent.sendMessage();
+            LL_INFOS("Messaging") << "AgentUpdate left button down" << LL_ENDL;
         }
-        else
+
+        if (control_flags & AGENT_CONTROL_LBUTTON_UP)
         {
-            gAgent.sendReliableMessage();
+            LL_INFOS("Messaging") << "AgentUpdate left button up" << LL_ENDL;
         }
+    }
 
-//      LL_DEBUGS("Messaging") << "agent " << avatar_pos_agent << " cam " << camera_pos_agent << LL_ENDL;
-
-        // Copy the old data
-        last_head_rot = head_rotation;
-        last_render_state = render_state;
-        last_camera_pos_agent = camera_pos_agent;
-        last_camera_at = LLViewerCamera::getInstance()->getAtAxis();
-        last_camera_left = LLViewerCamera::getInstance()->getLeftAxis();
-        last_camera_up = LLViewerCamera::getInstance()->getUpAxis();
-        last_control_flags = control_flags;
-        last_flags = flags;
+    if (send_reliable)
+    {
+        gAgent.sendReliableMessage();
+    }
+    else
+    {
+        gAgent.sendMessage();
     }
-}
 
+    // remember last update data
+    last_send_time = now;
+    last_control_flags = control_flags;
+    last_render_state = render_state;
+    last_flags = flags;
+    last_body_rot = body_rotation;
+    last_head_rot = head_rotation;
+    last_camera_pos_agent = camera_pos_agent;
+    last_camera_at = camera_at;
+}
 
 // sounds can arrive before objects, store them for a short time
 // Note: this is a workaround for MAINT-4743, real fix would be to make
-- 
cgit v1.2.3